{"owner":"xo","repo":"DBMETA","hasSkills":true,"totalSkillsCount":18,"totalTokensCount":39213,"categories":["anthropic-skill","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/go-pedantry/SKILL.md",".agents/skills/go-pedantry/examples/error-wrapping.md",".agents/skills/go-pedantry/examples/interface-design.md",".agents/skills/simple-english/SKILL.md",".agents/skills/simple-english/references/rule-catalog.md",".agents/skills/simple-english/references/strict-vocabulary.md",".agents/skills/simple-english/references/use-cases.md",".agents/skills/simple-english/references/word-swaps.md",".claude/skills/go-pedantry/SKILL.md",".claude/skills/go-pedantry/examples/error-wrapping.md",".claude/skills/go-pedantry/examples/interface-design.md",".claude/skills/simple-english/SKILL.md",".claude/skills/simple-english/references/rule-catalog.md",".claude/skills/simple-english/references/strict-vocabulary.md",".claude/skills/simple-english/references/use-cases.md",".claude/skills/simple-english/references/word-swaps.md","AGENTS.md","CLAUDE.md"],"skills":{".agents/skills/go-pedantry/SKILL.md":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint",".agents/skills/go-pedantry/examples/error-wrapping.md":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically",".agents/skills/go-pedantry/examples/interface-design.md":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable",".agents/skills/simple-english/SKILL.md":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n",".agents/skills/simple-english/references/rule-catalog.md":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n",".agents/skills/simple-english/references/strict-vocabulary.md":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n",".agents/skills/simple-english/references/use-cases.md":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n",".agents/skills/simple-english/references/word-swaps.md":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n",".claude/skills/go-pedantry/SKILL.md":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint",".claude/skills/go-pedantry/examples/error-wrapping.md":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically",".claude/skills/go-pedantry/examples/interface-design.md":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable",".claude/skills/simple-english/SKILL.md":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n",".claude/skills/simple-english/references/rule-catalog.md":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n",".claude/skills/simple-english/references/strict-vocabulary.md":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n",".claude/skills/simple-english/references/use-cases.md":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n",".claude/skills/simple-english/references/word-swaps.md":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n","AGENTS.md":"# dbmeta\n\n`dbmeta` holds database metadata queries and models in one Go module. `usql`\nand `dbtpl` consume it.\n\n`dbmeta` only reads. It does not render metadata and it does not write it. The\n`Reader` and `Writer` pair from `usql` is a `usql` concept and it does not\ncome here.\n\nOne statement is the exception and it is a narrow one. `Dialect.ChangePassword`\nbuilds the statement that sets a password and returns it as text, because the\nstatement and its escaping are per product knowledge and a password cannot be\nbound as a parameter. It takes no database and runs nothing, so everything\n`dbmeta` executes is still a read. Do not widen that. See D56.\n\nEvery quoting rule lives here, and none in a consumer. A rule two products\nshare goes in the root package, as `QuoteLiteral` and `QuoteIdentifier` do,\nand a product's own goes beside its model. See D127.\n\n## Standing rules\n\nThese hold in every `xo` repository, for every coding agent (D110).\n\n1. Stage changes for review. Commit and push only when Ken says so.\n2. Load the `simple-english` skill before you write any text that a person\n   reads: a document, a code comment, an error message or a commit message.\n   Follow it for that text.\n3. Load the `go-pedantry` skill before you write or review Go code. Follow it\n   where it does not conflict with a rule in this file. A rule here wins.\n\n`CLAUDE.md` holds one line that imports this file, so that Claude Code and\nevery other agent read the same rules. Edit this file, not that one.\n\n## Which document to read\n\nTwo before anything else. `docs/NULLS.md` is the shortest and the one that cost\nthe most to learn. `docs/decisions/` holds every decision, one file each, and\n`docs/decisions/README.md` is a table of all 246. Read the status, because 103\nof them amend or replace an earlier one. Do not decide an open question on\nyour own. They are at the end of `docs/PLAN.md`. Ask Ken.\n\nThen by what you are doing:\n\n| If you are | Read |\n| --- | --- |\n| writing or changing any query | `docs/NULLS.md`, then `docs/COVERAGE.md` |\n| asking why something is the way it is | the index in `docs/decisions/README.md` |\n| adding an object kind | D46 and D47 in `docs/decisions/`, then `docs/COMMANDS.md` |\n| adding a database | `docs/DIALECT.md`, which is every step in order. It points at D66, `docs/EVALUATION.md`, D43, D154, D38 and D61 |\n| wondering which database comes next | D66, which holds the order and why a product with no container is last |\n| a parity failure | D61. Decide whether the query began depending on who is asking, or whether one release genuinely differs, and record it |\n| changing what a database answers | `docs/COVERAGE.md`, which is the record of what each one can do |\n| changing a fixture | D53, then run the fixture test in the root module. A core object belongs in every fixture |\n| a conformance failure | D53. Decide whether it is a fault or a product difference, then fix it or rewrite the expectation and say why |\n| deciding whether a field belongs here | D47 in `docs/decisions/`, which holds the cost test |\n| wiring up a client | `docs/COMMANDS.md`, then `docs/USQL.md` or `docs/DBTPL.md` |\n| designing the object set | `docs/QUERIES.md`, the survey of psql against information_schema |\n| adding a release to CI | the product's file in `container/`, which is the only copy of its list |\n| adding an old SQL Server that needs a Windows VM | `docs/DIALECT.md` for where the steps differ, then `docs/WINDOWS.md`, `container/windows.go` and D57 |\n| starting a database for any reason | `dbrun`, and nothing else. `docs/DBRUN.md` holds its use and the rules for a shared machine. Read those rules first |\n| adding a container or a machine that dbrun starts | `docs/CONTAINERS.md`, every step in order |\n| reaching a hosted service, such as Snowflake | `docs/DBRUN.md`, under Hosted services, and D117. Never enter a credential yourself |\n| running a product that needs a license file, such as Stardog | `docs/DBRUN.md`, under License files, and D118. Never sign up for one or download one yourself |\n| adding an entry for a product dbimp or usql reaches | D118, which holds the rules the entries follow and what each one cost to measure |\n| changing how a database is started | D68, D70, D75, D86, D97, D98, D105, D108 and D115 in `docs/decisions/`, then `docs/DBRUN.md` |\n| changing how dbrun calls podman or docker | D122. Read JSON, name many containers in one call, and do not ask twice |\n| provisioning a Windows machine | `docs/WINDOWS.md`, then `container/windows.go` and D57 |\n| testing against Cassandra | `test/cmd/dbrun/image/cassandra.Containerfile`. `dbrun` embeds it and builds it when the image is missing |\n| changing the CI workflow | D69. It reads the release list from `dbrun list --json` and names no image of its own |\n| answering a lint finding | the rule below, under Linting |\n| ignoring a build artifact | the root `.gitignore`, which is the only one. See D58 |\n| writing a document, a code comment, an error message or a commit message | the `simple-english` skill. Load it first. See below, under Writing documentation |\n| writing or reviewing Go code | the `go-pedantry` skill. Load it first. See Standing rules |\n| looking for work that is known and not done | `docs/BACKLOG.md` |\n| resuming a session that ended or crashed | `docs/PROGRESS.md`, which says where the work stands |\n| adding or updating an agent skill | `CONTRIBUTING.md`, under Agent skills, D89 and D110 |\n\n`CONTRIBUTING.md` is the same thing for a person, and shorter.\n\nA document that is not in that table does not exist. If you cannot find where\nsomething is written down, it is not written down, and it is an open question.\n\n## Hard rules\n\n1. The root module depends on the standard library and on nothing else. Its\n   `go.mod` has no `require` block and must keep it that way. Never add a\n   database driver, not even for a test. A consumer brings its own driver and\n   picks its own version of it, and `dbmeta` must not constrain that. Anything\n   needing a driver goes in the `test` module. Do not add any third party\n   package without asking Ken first.\n   D19 once made `github.com/xo/dburl` a direct dependency. It never became\n   one, because nothing here opens a connection: the caller passes a `DB` and\n   names a `Dialect`, so there is no URL to parse. That is the better answer\n   and it stands.\n   The rule that outlived the dependency is this one. Never write a scheme\n   list, an alias list, a flavor table, or a connection string parser in this\n   module. That taxonomy is `dburl`'s and repeating it is how two copies start\n   disagreeing. A consumer that has a URL reads `dburl` itself:\n   `URL.Dialect`, from dburl v0.32.0, is the dbmeta `Dialect` and selects the\n   model package, and `URL.OriginalScheme` holds an alias such as `mariadb`.\n   A product that speaks another product's protocol, such as `cockroachdb`\n   or `tidb`, has a `Dialect` of its own from dburl v0.36.0, so nothing else\n   names it. `URL.Driver` is the name `sql.Open` takes and never the dialect:\n   `pgx://` and `postgres://` both have the dialect `postgres`. See D99 and\n   D125.\n2. PostgreSQL is the primary model, and `psql` defines it. When two databases\n   describe the same object differently, follow `psql`. This decides the shape\n   of an answer. It does not require every database to answer every question.\n   `psql` sets the object model. It does not set the column set, and it never\n   did: `psql` prints what a person reads at a terminal, and a consumer needs\n   the parts. Return a fact `psql` does not print when one statement can\n   produce it, and never return prose as the only form of anything. D47 holds\n   the cost test, and rule 13 is the short version.\n3. A query that must differ between releases is one query with version\n   fragments, held as data in the one model package. There is no\n   package per release. A fragment must never change the set of columns a\n   query returns. Whichever side lacks a source pads, old or new: select the\n   column as `NULL AS name` on the server that has no source for it. A type\n   that differs between versions is cast to a common one, never left to `any`.\n   Where two products share a dialect, a fragment for one of them gates on\n   that product's version key and never on the number alone. MariaDB 11.8 and\n   MySQL 9 have no numeric relation, so a gate at `V(10, 2)` silently means\n   \"MariaDB only\" and shipped a wrong answer once already. Write\n   `Gate{Key: MariaDB, Min: V(10, 2)}`. Set a key only for a product you\n   detected. See D44.\n4. Stay backward compatible within reason. An old database keeps working when\n   support for a new one arrives. Every version sits in one of five tiers:\n   Tested in CI on every push, Nightly in CI once a night, Verified on a\n   development machine before a release and never in CI, Staged when no\n   model reads it yet, or Archived with no tests at all. Never call a version\n   supported without naming its tier, and never call a Staged one supported\n   at all. D40 set three, D42 added Nightly and D119 added Staged. The first\n   four are values of `container.Tier` and Archived is not, because an\n   archived release is one `container.All` does not name. A release is Staged\n   exactly when no model reads it. A Staged release records the cadence it\n   will have, Tested, Nightly or Verified, and takes it as its tier in the\n   change that adds its model. See D40, D42, D119 and D120.\n5. A query translated from a source tree that upstream no longer ships records\n   the release and commit of that tree beside the query. PostgreSQL 9.6 is the\n   case: `psql` dropped it in release 20, so there is nothing current to check\n   a translation against.\n6. Configuration is a value that the caller owns. Do not put a configured\n   value in a package level variable. One driver borrowed another driver's\n   configuration in `usql` and shipped a fault to users.\n7. Do not import `usql` or `dbtpl`. The dependency runs the other way.\n8. Every function that reads metadata takes `ctx context.Context` as its first\n   parameter. There are no exceptions. Call `QueryContext` and its relatives,\n   never `Query`, `Exec`, `QueryRow`, or `Prepare`. Never call\n   `context.Background()` or `context.TODO()` inside this library.\n9. Every model ships both its queries and its fixtures. The fixture creates one\n   of every object the queries read, so a test gets rows worth checking. It is\n   versioned like the queries, because syntax and objects arrive in different\n   releases, and a step the server is too old for is skipped rather than\n   refused. A query that has never run against a real server is not finished.\n10. The root module is pure Go and has no cgo, ever. It has no driver at all,\n   so there is nothing to argue about: a consumer builds it with\n   `CGO_ENABLED=0` and cross compiles it.\n   The `test` module can use cgo, because its own `go.mod` keeps it out of\n   everything a consumer builds. Where a database has a canonical cgo driver\n   and a pure Go one, test both: the canonical driver is what users run, and\n   the pure Go one is what a consumer who cannot use cgo runs, and the two\n   ship different library versions. Two drivers need cgo today, and only two:\n   `mattn/go-sqlite3`, which builds the real SQLite source and is the primary\n   SQLite driver, and `duckdb/duckdb-go`.\n   Never `godror`, which needs Oracle client libraries rather than only a C\n   compiler. See D48.\n   Compare the version query too. Adding or changing a dialect means reading\n   what `usql` runs for the same product, in the `Version` field of its\n   `drivers.Driver`, and recording the comparison in the statements table in\n   `docs/USQL.md`. A driver declaring no `Version` falls through to the generic\n   `SELECT version();`, and that counts as its statement. Read the field and\n   not the `drivers.Register` call: Oracle and godror both register through\n   `orshared.Register`, so a grep for the call says neither has a version\n   query and both do. The Oracle defect is real and it is a different one.\n   `usql` reads `v$instance`, which an ordinary user cannot see, so it prints\n   a version for an administrator and none for anybody else. Comparing\n   printed output never found that. `docs/USQL.md` holds the measurement.\n   `TestEveryModelIsInTheVersionTable` fails when a model has no row. See D38.\n   Every driver in the `test` module is the one the `dburl` registry names for\n   that dialect: `Scheme.GoPackage` of each scheme whose `Dialect` it is, from\n   dburl v0.29.0, with `Scheme.RequiresCGO` saying whether it needs a C\n   compiler. dburl is upstream of `dbmeta` and of `usql` both, and `usql` is\n   downstream, so its imports do not decide this. The version is the test\n   module's own, because the registry carries none. Where two schemes of one\n   dialect name two packages, test both as subtests named for the driver:\n   SQLite runs on `mattn/go-sqlite3` and `modernc.org/sqlite`, and PostgreSQL\n   on `jackc/pgx/v5/stdlib` and `lib/pq`. Parity is the one exception, and\n   D52 says why. The `go-ora/v3` release that dburl names panics rather than\n   connecting on 11g and 18c, and the fix is upstream and untagged. So the\n   Oracle tests pin v3 at the commit that fixes it, and move to the tag when\n   one holds the fix (D59, D157). If what `usql` imports differs from\n   what dburl names, the fault is in one of those two, and the fix goes\n   there. A query that works here and fails on the driver dburl names is a\n   query that does not work. See D52, D80 and D154.\n11. Never write a `//go:build` constraint on an operating system or an\n   architecture, and never branch on either. Testing is `linux/amd64` only.\n   The same database version is assumed to answer the same way everywhere.\n   A driver can carry its own platform builds, which is the driver's business.\n12. Write idiomatic Go. This code is a move of an older package, so a pattern\n   being present in the source is not a reason to keep it. See D18 in\n   `docs/decisions/` for the two patterns that must not carry over.\n13. `dbmeta` supplies the data and the consumer decides what to show. Never\n   withhold a fact, reorder one, or shape a result so that somebody's output\n   looks right. `usql` filters to match `psql` and `dbtpl` shows none of it,\n   and one set of queries serves both because no presentation is baked in.\n   A field can be added when one statement can produce it: a column already in\n   the row, a column reached by a join, or a correlated subquery whose plan\n   stays bounded as the catalog grows. It cannot when it needs a second\n   statement, a per row round trip, or a scan that grows with the whole\n   catalog. Check a new one with `EXPLAIN` against a catalog with thousands of\n   tables, not against a fixture with five.\n   A child of an object is its own kind with flat rows, never a slice on the\n   parent, because filling a slice needs a second statement or a dialect\n   specific aggregate. See D47.\n14. A new dialect is not finished until you ask several AI models\n   about the queries it cannot answer. `docs/DIALECT.md` holds every step of\n   adding one, and this is one of them. Consult at least two of Gemini,\n   DeepSeek and Astra, and ask each one to sort the unanswered queries into\n   three groups: absent from the product, present under another name, and\n   derivable from several catalog reads or one complex statement. A first pass\n   only finds the objects that the product names the way PostgreSQL names\n   them, and MariaDB proved the cost: 29 queries looked unanswerable until a\n   second opinion named the tables that hold four of them. Treat every answer\n   as a lead and run it against a real server. Leave an analogue that is a\n   stretch unsupported, and record it in `docs/COVERAGE.md` with the reason. See\n   D43.\n15. Never start a container by hand. `test/cmd/dbrun` starts every database\n   this project uses, and every container is named `<product>-<release>`, such\n   as `postgres-18` or `clickhouse-26.9`. Run\n   `cd test && go run ./cmd/dbrun help`, which lists every command, and\n   `docs/DBRUN.md` holds the same table, and the rules for sharing this\n   machine with other sessions. It takes `all` or a tier name\n   where it takes a server. The commands most often wanted are `start`,\n   `stop`, `remove`, `status`, `version`, `dsn`, `usql` and `test`. The rest\n   are in that table and this file does not repeat them.\n   This is not tidiness. A container started by hand gets a port somebody\n   typed rather than the one `container.All` assigns, so `dbrun version`\n   cannot reach a server that is plainly running, and two copies of the same\n   release end up on the machine with nothing to tell them apart. See D68.\n   If you are a coding agent, set `DBMETA_OWNER_NAME` to the name of your\n   session on every `dbrun` command, so that `status` shows whose each server\n   is. See D115.\n16. A dialect is not finished until you ask every query as the\n   administrator and as every lesser kind of principal the product has, and\n   the differences are written down. Add the principals to `parityTargets` in\n   `test/parity_test.go`, run `go test -run TestPrivilegeParity -update`, and\n   read the diff.\n   A principal is not one thing. SQL Server has three: a sysadmin, a server\n   login mapped to a database user, and a contained database user whose\n   password is in the database and which has no login at the server. Oracle\n   has the same three from 12c, where a common user is the login and a local\n   user in a pluggable database is the contained user. PostgreSQL and MySQL\n   have no containment, so each has a superuser or root, an owner, and a\n   grantee. SQLite and DuckDB have no user at all and the rule cannot reach\n   them.\n   Parity ships with the queries, the fixture and the documentation. They are\n   one deliverable. `TestEveryDialectIsMeasuredForParity` fails when a dialect\n   has neither a target nor an entry in `parityExempt` saying why it has none,\n   because `TestPrivilegeParity` says nothing about a target nobody wrote.\n   A scene the server is too old for carries a `min` and is skipped with the\n   reason, the way a fixture step is.\n   This is not a formality. It found six MariaDB queries that are refused\n   outright for a user holding ALL PRIVILEGES on its own database, because\n   they read tables in the `mysql` database rather than views that filter\n   themselves. Nothing had recorded that. See D61.\n\n## Layout\n\n- `/` is the root package `dbmeta`. It holds the driver agnostic API: the\n  object types, the `Query` values, the one method `Queryer` interface, the\n  `Args` filter, and the error values. External projects use this package.\n- `models/<driver>` holds one model, written by hand. One package covers every\n  supported version of that database. For example, `models/sqlite3`. A model\n  registers its queries from `init` and nothing generates it, so edit it\n  directly. See D71.\n- `all/` holds one file per model, such as `all/postgres.go`, each a blank\n  import gated by a build tag. Importing `all` registers the base set, and\n  `no_base`, a model's own name and `no_<model>` change which. A model can be\n  imported from `models/<driver>` instead. Say model, not driver: `dbmeta`\n  never opens a connection. See D31.\n- `container/` names every database release the tests run against, as Go data.\n  It starts no container and imports no container client. `test/cmd/dbrun` and\n  the CI workflow both read it, and a test fails when they drift.\n- `hosted/` names the hosted services, such as Snowflake and BigQuery, as Go\n  data. It holds no secret. `dbrun` resolves each one's connection string at\n  run time, and a service exists in `dbrun` only while it resolves. See D117.\n- `test/cmd/dbrun` is the only thing that starts a database, container or\n  virtual machine alike. `container/machine.go` holds the machine list, and\n  `container/windows.go` holds the Windows machines in it. `dbrun provision`\n  builds a Windows machine from the payload it embeds in\n  `test/cmd/dbrun/oem/`, and imports an appliance from the file a person\n  downloaded. Read `docs/WINDOWS.md`. See D57, D68 and D86.\n- `test/cmd/dbrun/image/` holds the Containerfiles this repository builds,\n  one for each product whose image dbrun builds. `dbrun` embeds them all and\n  builds `<product>.Containerfile` as `localhost/dbmeta/<product>`. See D118.\n  The Apache Cassandra image cannot be configured from the outside for what\n  the queries read, so the settings are baked in. `dbrun` embeds the file and\n  builds the image when it is missing.\n- `test/` is a separate module with its own `go.mod`. It holds the integration\n  tests and the database drivers. Neither can appear in the root module. It is\n  the only place cgo is allowed, and the separate `go.mod` is what makes that\n  safe.\n\nRead an existing model before you write one. `models/postgres` is the primary\none and `models/sqlite3` is a small one.\n\n## Writing a model\n\nNothing generates the code here. Every model is written, and no tool produces\nor reproduces it, so a file under `models/` is edited in place. See D71.\n\nA model is one package that registers a `Binding` per object kind from `init`.\nThe `Binding` carries the statement as `Stmt`, the `Fields` it returns, the\n`Params` it takes and a `Scan` function. A query that must differ between\nreleases is one statement with version fragments, which is rule 3.\n\nWrite the statement against a running server rather than from the\ndocumentation. `dbrun` is the only thing that starts one:\n\n```bash\ncd test && go run ./cmd/dbrun start postgres && go run ./cmd/dbrun usql postgres\n```\n\n`dbrun start` leaves the server up, which is what writing a query needs.\nRemove it with `dbrun remove postgres` when you are done. See D68.\n\nTwo rules catch the recurring NULL scan fault. Give a field `sql.Null[T]`\nwhenever the catalog column can be NULL, and select `NULL AS \"name\"` rather\nthan a literal where a release has no source for a column. A column that the\ncatalog declares NOT NULL can still arrive NULL through an outer join, so read\n`docs/NULLS.md` before deciding.\n\nA model is not finished until it has run against every release in its tier,\nits fixture builds one of every object its queries read, and its parity\ntargets exist. Those are one deliverable. See rule 9, rule 16 and D61.\n\n## Go conventions\n\nWrap every error with `%w`, never `%s` or `%v`:\n\n```go\nif err != nil {\n    return nil, fmt.Errorf(\"reading columns for %s: %w\", table, err)\n}\n```\n\nWrite error messages in lower case, starting with a gerund. Do not write\n\"failed to\" or \"error\". Name the object that failed.\n\nNever hide a NULL. Read `docs/NULLS.md` before writing a query for any database. It\nis the shortest document here and it is the one that has cost the most to\nlearn.\n\nIn brief: never wrap a nullable catalog column in `COALESCE`, and never pad an\nabsent column with a literal. Give the field the type `sql.Null[string]`, or\n`sql.Null[T]` for whatever T it is, and select `NULL AS \"name\"`. There is no\nalias for a nullable type and there must not be one: a reader of\n`go doc dbmeta.Sequence` learns from the field itself that it can be absent,\nand does not learn it from a name like `Text`. See D51. Before padding, ask whether the value on the old release is\nunknown or genuinely that value, and set `Field.Min` only for the first.\n\nDeclare sentinel errors as constants of a defined string type, never as\nvariables. A `var` declared with `errors.New` can be reassigned by any\nimporter. A constant cannot.\n\n```go\n// Error is an error.\ntype Error string\n\n// Error satisfies the error interface.\nfunc (err Error) Error() string {\n\treturn string(err)\n}\n\n// Error values.\nconst (\n\t// ErrNotSupported is the not supported error.\n\tErrNotSupported Error = \"not supported\"\n)\n```\n\nKeep the `Err` prefix and the PascalCase name. Compare with `errors.Is`.\n\nAccept interfaces and return concrete structs. Keep an interface to three\nmethods or fewer. Define an interface where it is consumed, not where it is\nimplemented.\n\nDo not name a type `Reader`. Everything in this module reads, so the word adds\nnothing and repeats the package.\n\nReturn an iterator, `iter.Seq2[Table, error]`. Read the result one record at a\ntime and never materialize it into a slice. Do not write a cursor type. An\nerror can arrive part way through a result, so a caller must be able to tell\nthe end of a result from a failure in the middle of one.\n\nAn iterator holds a database connection until it ends. Stopping early, by\n`break` or by `yield` returning false, must close the rows and release the\nconnection, and so must canceling the context. A caller that nests one\niterator inside another uses a second connection, which deadlocks on a pool of\none. Document the filter form that avoids nesting.\n\nUse generics rather than `interface{}` for a container of one type. Compare\nerrors with `errors.Is` and `errors.As`, never by string.\n\nPut `context.Context` first in every parameter list and name it `ctx`. Never\nstore it in a struct.\n\nName a package with one short lower case word. Do not stutter: write\n`postgres.Reader`, not `postgres.PostgresReader`.\n\nName a receiver with one or two lower case letters, and use the same name on\nevery method of the type. Never write `this` or `self`.\n\nGroup struct fields by purpose, with exported fields first and a blank line\nbetween groups.\n\n## Linting\n\n`.golangci.yml` holds the rules, and `test/.golangci.yml` holds them for the\ntest module. golangci-lint reads the `go.mod` of the directory it runs in, so\nboth are linted separately and CI runs it twice.\n\n```bash\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\nThe posture is `default: all` with a disable list. A new linter gets a look\nrather than silence, and that has paid: turning it on found a missing\n`rows.Err` check, a parameter shadowing the `min` builtin, and a copy loop that\n`maps.Copy` now does.\n\n### The rule for a lint finding\n\nA linter that makes idiomatic Go worse is disabled, with the reason written\nbeside it in the configuration. Only a real defect gets a code change. Ask\nwhich one it is before you touch the code, and if the remedy reads worse than\nwhat it replaces, that is the answer.\n\nA change made to quiet a linter rather than to fix something is itself a\ndefect. It leaves code that no reader can explain and that the next person\ncopies. Two were written here and reverted, and they are named so that they do\nnot come back:\n\nNever write `defer func() { _ = rows.Close() }()`. Write `defer rows.Close()`.\nA deferred `Close` returns an error nothing can act on, and the error that\nmatters was already reported by `Err`. `errcheck` is configured to allow it.\n\nNever add an empty `case X:` to satisfy `exhaustive`. A switch that handles two\nof three values and answers the third after the switch says more than a case\nwith no body. `exhaustive` is disabled.\n\nA guard that can never fire is not a fix. `gosec` wanted a bound on a\nconversion of a PostgreSQL `server_version_num`, which is six digits, and the\nguard first written for it tested the wrong quantity and was never able to\nfire. That warning is excluded with its reason.\n\nRead `golangci-lint run` output as a list of questions, not a list of tasks.\n\n## Container images\n\n`container/` names every server release that `dbrun` starts, as Go data, one\nfile per product, and `container.All` in `container/container.go` joins them.\nMost are what dbmeta is tested against. The rest are Staged, because no model\nreads them: they are there for dbimp's drivers, for the flavors usql reaches,\nand for the emulators of hosted services, and CI does not run them (D118,\nD119). The package starts\nnothing and imports no container client, and it must not: a consumer brings\nits own podman, docker or Go client and picks its own version of it, the same\nway it brings its own driver.\n\nAn embedded database is not in there and must not be. SQLite3, DuckDB,\nmoderncsqlite, chai and csvq have no server and no container, and the\nrelease is whichever one the pinned Go driver ships. `dbrun` knows them\nitself (D116), and CI tests the ones a model reads in the same matrix as the\nservers, where `dbrun` starts nothing for them. chai and csvq have no\nmodel yet, so they are Staged (D119). See D42.\n\nThat list is the only copy. `test/cmd/dbrun` reads it, the CI workflow builds\nits matrix from `dbrun list --json --names`, and\n`container/workflow_test.go` fails when the workflow and the Go list disagree.\nChange the product's file in `container/` first and the workflow second.\n\nAdd a release there before you add it anywhere else.\n\n## Before you commit\n\nRun these:\n\n```bash\ngofmt -l . && go vet ./... && go build ./... && go test -race -count=2 ./...\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\n`gofmt -l .` must print nothing.\n\nRun it with `-count=2`, because that is what CI runs and a weaker local command\nhas already let two failures through. Both were the same fault: a test body\nthat registers a dialect or a query, which a registry rejects the second time.\nRegistration belongs in `init`.\n\nThat does not cover the `test` module. `./...` does not descend into a\ndirectory that has its own `go.mod`, so run the integration tests separately:\n\n```bash\ncd test && go test ./...\n```\n\nA test in the root module never opens a database connection. Test fragment\nmerging, version parsing and model selection there, against golden files. A\ntest that needs a server belongs in the `test` module.\n\nCI runs on GitHub Actions, on `ubuntu-latest` only. The queries are SQL and the\ncode is pure Go, so `dbmeta` does not need the runner matrix that the other\n`xo` projects use. Keep the workflow small.\n\nCI runs a release matrix, which D42 decided and which replaced the single\nlatest version D24 first called for. Every push runs the Tested tier, and\n`container.AtTier(container.Tested)` is the list. This file does not repeat\nit, because the copy written here went stale the moment a product was added,\nand it named six products for as long as ClickHouse, Trino, Presto, Firebird,\nSAP HANA and Apache Hive went on being added to the tier without it. Read the\nlist with:\n\n```bash\ncd test && go run ./cmd/dbrun list --json --names tested\n```\n\nThat command adds the embedded databases that a model reads, which are not\nin `container.AtTier` at all, and `dbrun` starts nothing for them. D42 keeps an\nembedded database out of the release list, and `dbrun` puts them back because\na person asking for the tier wants to run them too. The Nightly tier runs\nonce a night.\n\n`dbrun` builds the Cassandra image before it tests a Cassandra release, from\nthe Containerfile it embeds, because the published image refuses what the\nqueries read. See D62 and `test/cmd/dbrun/image/cassandra.Containerfile`.\n\nDo not write that list in the workflow from memory. It lives in `container/`,\n`container.AtTier` selects a tier, and\n`TestWorkflowReadsTheList` fails when the YAML and the Go disagree. Change the\nGo first and then the YAML. See D42 and D54.\n\nCI uses no database that is preinstalled on the runner. `dbrun` starts every\nrelease, and the compare job runs MariaDB 13.0 and MySQL 26.7 as service\ncontainers to check that the two products answer the same way (D44).\n\n## Writing documentation\n\nA new document goes in `docs/`. Only `README.md`, `AGENTS.md`, `CLAUDE.md` and\n`CONTRIBUTING.md` belong in the repository root, and a test enforces that. Add\nit to the table at the top of this file and to the one in `README.md`, because\na document nobody can find is a document nobody reads. See D50.\n\nA decision goes in a file of its own in `docs/decisions/` and nowhere else\n(D111). Name it `D<nnn>-<title>.md` with the next number. It opens with\n`# D<n>. <title>`, a blank line, and `Status: <status>.`, where the status is\n`Decided`, or `Amends D26`, or `Superseded by D24`. Add its row to\n`docs/decisions/README.md`. `TestTheDecisionIndexIsComplete` prints the row\nwhen it is missing. If your decision changes an earlier one, say so in both\nstatuses, because a reader who finds the older one has to be told.\n\nWrite plain English. Use short sentences and the active voice. Use `can`,\n`will`, and `must`, and do not use `should`, `may`, or `might`. Do not use\nsemicolons or em dashes. Put the condition before the command: \"If the query\nreturns no rows, return the empty set.\"\n\nLoad the `simple-english` skill before you write any text that a person reads:\na document, a code comment, an error message or a commit message. Follow it\nfor that text. Its rules include the ones above and add more, such as no\ncontractions and one word for one meaning. Ken asked for this. See D89 and\nD110.\n\n`TestProseIsSimpleEnglish` checks the rules of the skill that a machine can\ncheck: the banned modals, semicolons, dashes, contractions, bold, \"has been\",\nBritish spellings, filler words and Latin abbreviations. It reads every\ndocument, every comment in Go, YAML, a Containerfile, a batch file or an INI\nfile, and every error and test message. It skips text\nin backticks or double quotes. There is no marker that turns a rule off. If\nit reports a sentence, rewrite the sentence. Sentence length and the voice\nare still yours to check. See D156.\n\nWork that is known and not done goes in `docs/BACKLOG.md`, with the decision\nor the measurement that found it. When an item is done, delete it, and record\nanything decided in `docs/decisions/`.\n","CLAUDE.md":"@AGENTS.md\n"},"files":{".agents/skills/go-pedantry/SKILL.md":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint",".agents/skills/go-pedantry/examples/error-wrapping.md":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically",".agents/skills/go-pedantry/examples/interface-design.md":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable",".agents/skills/simple-english/SKILL.md":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n",".agents/skills/simple-english/references/rule-catalog.md":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n",".agents/skills/simple-english/references/strict-vocabulary.md":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n",".agents/skills/simple-english/references/use-cases.md":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n",".agents/skills/simple-english/references/word-swaps.md":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n",".claude/skills/go-pedantry/SKILL.md":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint",".claude/skills/go-pedantry/examples/error-wrapping.md":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically",".claude/skills/go-pedantry/examples/interface-design.md":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable",".claude/skills/simple-english/SKILL.md":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n",".claude/skills/simple-english/references/rule-catalog.md":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n",".claude/skills/simple-english/references/strict-vocabulary.md":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n",".claude/skills/simple-english/references/use-cases.md":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n",".claude/skills/simple-english/references/word-swaps.md":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n","AGENTS.md":"# dbmeta\n\n`dbmeta` holds database metadata queries and models in one Go module. `usql`\nand `dbtpl` consume it.\n\n`dbmeta` only reads. It does not render metadata and it does not write it. The\n`Reader` and `Writer` pair from `usql` is a `usql` concept and it does not\ncome here.\n\nOne statement is the exception and it is a narrow one. `Dialect.ChangePassword`\nbuilds the statement that sets a password and returns it as text, because the\nstatement and its escaping are per product knowledge and a password cannot be\nbound as a parameter. It takes no database and runs nothing, so everything\n`dbmeta` executes is still a read. Do not widen that. See D56.\n\nEvery quoting rule lives here, and none in a consumer. A rule two products\nshare goes in the root package, as `QuoteLiteral` and `QuoteIdentifier` do,\nand a product's own goes beside its model. See D127.\n\n## Standing rules\n\nThese hold in every `xo` repository, for every coding agent (D110).\n\n1. Stage changes for review. Commit and push only when Ken says so.\n2. Load the `simple-english` skill before you write any text that a person\n   reads: a document, a code comment, an error message or a commit message.\n   Follow it for that text.\n3. Load the `go-pedantry` skill before you write or review Go code. Follow it\n   where it does not conflict with a rule in this file. A rule here wins.\n\n`CLAUDE.md` holds one line that imports this file, so that Claude Code and\nevery other agent read the same rules. Edit this file, not that one.\n\n## Which document to read\n\nTwo before anything else. `docs/NULLS.md` is the shortest and the one that cost\nthe most to learn. `docs/decisions/` holds every decision, one file each, and\n`docs/decisions/README.md` is a table of all 246. Read the status, because 103\nof them amend or replace an earlier one. Do not decide an open question on\nyour own. They are at the end of `docs/PLAN.md`. Ask Ken.\n\nThen by what you are doing:\n\n| If you are | Read |\n| --- | --- |\n| writing or changing any query | `docs/NULLS.md`, then `docs/COVERAGE.md` |\n| asking why something is the way it is | the index in `docs/decisions/README.md` |\n| adding an object kind | D46 and D47 in `docs/decisions/`, then `docs/COMMANDS.md` |\n| adding a database | `docs/DIALECT.md`, which is every step in order. It points at D66, `docs/EVALUATION.md`, D43, D154, D38 and D61 |\n| wondering which database comes next | D66, which holds the order and why a product with no container is last |\n| a parity failure | D61. Decide whether the query began depending on who is asking, or whether one release genuinely differs, and record it |\n| changing what a database answers | `docs/COVERAGE.md`, which is the record of what each one can do |\n| changing a fixture | D53, then run the fixture test in the root module. A core object belongs in every fixture |\n| a conformance failure | D53. Decide whether it is a fault or a product difference, then fix it or rewrite the expectation and say why |\n| deciding whether a field belongs here | D47 in `docs/decisions/`, which holds the cost test |\n| wiring up a client | `docs/COMMANDS.md`, then `docs/USQL.md` or `docs/DBTPL.md` |\n| designing the object set | `docs/QUERIES.md`, the survey of psql against information_schema |\n| adding a release to CI | the product's file in `container/`, which is the only copy of its list |\n| adding an old SQL Server that needs a Windows VM | `docs/DIALECT.md` for where the steps differ, then `docs/WINDOWS.md`, `container/windows.go` and D57 |\n| starting a database for any reason | `dbrun`, and nothing else. `docs/DBRUN.md` holds its use and the rules for a shared machine. Read those rules first |\n| adding a container or a machine that dbrun starts | `docs/CONTAINERS.md`, every step in order |\n| reaching a hosted service, such as Snowflake | `docs/DBRUN.md`, under Hosted services, and D117. Never enter a credential yourself |\n| running a product that needs a license file, such as Stardog | `docs/DBRUN.md`, under License files, and D118. Never sign up for one or download one yourself |\n| adding an entry for a product dbimp or usql reaches | D118, which holds the rules the entries follow and what each one cost to measure |\n| changing how a database is started | D68, D70, D75, D86, D97, D98, D105, D108 and D115 in `docs/decisions/`, then `docs/DBRUN.md` |\n| changing how dbrun calls podman or docker | D122. Read JSON, name many containers in one call, and do not ask twice |\n| provisioning a Windows machine | `docs/WINDOWS.md`, then `container/windows.go` and D57 |\n| testing against Cassandra | `test/cmd/dbrun/image/cassandra.Containerfile`. `dbrun` embeds it and builds it when the image is missing |\n| changing the CI workflow | D69. It reads the release list from `dbrun list --json` and names no image of its own |\n| answering a lint finding | the rule below, under Linting |\n| ignoring a build artifact | the root `.gitignore`, which is the only one. See D58 |\n| writing a document, a code comment, an error message or a commit message | the `simple-english` skill. Load it first. See below, under Writing documentation |\n| writing or reviewing Go code | the `go-pedantry` skill. Load it first. See Standing rules |\n| looking for work that is known and not done | `docs/BACKLOG.md` |\n| resuming a session that ended or crashed | `docs/PROGRESS.md`, which says where the work stands |\n| adding or updating an agent skill | `CONTRIBUTING.md`, under Agent skills, D89 and D110 |\n\n`CONTRIBUTING.md` is the same thing for a person, and shorter.\n\nA document that is not in that table does not exist. If you cannot find where\nsomething is written down, it is not written down, and it is an open question.\n\n## Hard rules\n\n1. The root module depends on the standard library and on nothing else. Its\n   `go.mod` has no `require` block and must keep it that way. Never add a\n   database driver, not even for a test. A consumer brings its own driver and\n   picks its own version of it, and `dbmeta` must not constrain that. Anything\n   needing a driver goes in the `test` module. Do not add any third party\n   package without asking Ken first.\n   D19 once made `github.com/xo/dburl` a direct dependency. It never became\n   one, because nothing here opens a connection: the caller passes a `DB` and\n   names a `Dialect`, so there is no URL to parse. That is the better answer\n   and it stands.\n   The rule that outlived the dependency is this one. Never write a scheme\n   list, an alias list, a flavor table, or a connection string parser in this\n   module. That taxonomy is `dburl`'s and repeating it is how two copies start\n   disagreeing. A consumer that has a URL reads `dburl` itself:\n   `URL.Dialect`, from dburl v0.32.0, is the dbmeta `Dialect` and selects the\n   model package, and `URL.OriginalScheme` holds an alias such as `mariadb`.\n   A product that speaks another product's protocol, such as `cockroachdb`\n   or `tidb`, has a `Dialect` of its own from dburl v0.36.0, so nothing else\n   names it. `URL.Driver` is the name `sql.Open` takes and never the dialect:\n   `pgx://` and `postgres://` both have the dialect `postgres`. See D99 and\n   D125.\n2. PostgreSQL is the primary model, and `psql` defines it. When two databases\n   describe the same object differently, follow `psql`. This decides the shape\n   of an answer. It does not require every database to answer every question.\n   `psql` sets the object model. It does not set the column set, and it never\n   did: `psql` prints what a person reads at a terminal, and a consumer needs\n   the parts. Return a fact `psql` does not print when one statement can\n   produce it, and never return prose as the only form of anything. D47 holds\n   the cost test, and rule 13 is the short version.\n3. A query that must differ between releases is one query with version\n   fragments, held as data in the one model package. There is no\n   package per release. A fragment must never change the set of columns a\n   query returns. Whichever side lacks a source pads, old or new: select the\n   column as `NULL AS name` on the server that has no source for it. A type\n   that differs between versions is cast to a common one, never left to `any`.\n   Where two products share a dialect, a fragment for one of them gates on\n   that product's version key and never on the number alone. MariaDB 11.8 and\n   MySQL 9 have no numeric relation, so a gate at `V(10, 2)` silently means\n   \"MariaDB only\" and shipped a wrong answer once already. Write\n   `Gate{Key: MariaDB, Min: V(10, 2)}`. Set a key only for a product you\n   detected. See D44.\n4. Stay backward compatible within reason. An old database keeps working when\n   support for a new one arrives. Every version sits in one of five tiers:\n   Tested in CI on every push, Nightly in CI once a night, Verified on a\n   development machine before a release and never in CI, Staged when no\n   model reads it yet, or Archived with no tests at all. Never call a version\n   supported without naming its tier, and never call a Staged one supported\n   at all. D40 set three, D42 added Nightly and D119 added Staged. The first\n   four are values of `container.Tier` and Archived is not, because an\n   archived release is one `container.All` does not name. A release is Staged\n   exactly when no model reads it. A Staged release records the cadence it\n   will have, Tested, Nightly or Verified, and takes it as its tier in the\n   change that adds its model. See D40, D42, D119 and D120.\n5. A query translated from a source tree that upstream no longer ships records\n   the release and commit of that tree beside the query. PostgreSQL 9.6 is the\n   case: `psql` dropped it in release 20, so there is nothing current to check\n   a translation against.\n6. Configuration is a value that the caller owns. Do not put a configured\n   value in a package level variable. One driver borrowed another driver's\n   configuration in `usql` and shipped a fault to users.\n7. Do not import `usql` or `dbtpl`. The dependency runs the other way.\n8. Every function that reads metadata takes `ctx context.Context` as its first\n   parameter. There are no exceptions. Call `QueryContext` and its relatives,\n   never `Query`, `Exec`, `QueryRow`, or `Prepare`. Never call\n   `context.Background()` or `context.TODO()` inside this library.\n9. Every model ships both its queries and its fixtures. The fixture creates one\n   of every object the queries read, so a test gets rows worth checking. It is\n   versioned like the queries, because syntax and objects arrive in different\n   releases, and a step the server is too old for is skipped rather than\n   refused. A query that has never run against a real server is not finished.\n10. The root module is pure Go and has no cgo, ever. It has no driver at all,\n   so there is nothing to argue about: a consumer builds it with\n   `CGO_ENABLED=0` and cross compiles it.\n   The `test` module can use cgo, because its own `go.mod` keeps it out of\n   everything a consumer builds. Where a database has a canonical cgo driver\n   and a pure Go one, test both: the canonical driver is what users run, and\n   the pure Go one is what a consumer who cannot use cgo runs, and the two\n   ship different library versions. Two drivers need cgo today, and only two:\n   `mattn/go-sqlite3`, which builds the real SQLite source and is the primary\n   SQLite driver, and `duckdb/duckdb-go`.\n   Never `godror`, which needs Oracle client libraries rather than only a C\n   compiler. See D48.\n   Compare the version query too. Adding or changing a dialect means reading\n   what `usql` runs for the same product, in the `Version` field of its\n   `drivers.Driver`, and recording the comparison in the statements table in\n   `docs/USQL.md`. A driver declaring no `Version` falls through to the generic\n   `SELECT version();`, and that counts as its statement. Read the field and\n   not the `drivers.Register` call: Oracle and godror both register through\n   `orshared.Register`, so a grep for the call says neither has a version\n   query and both do. The Oracle defect is real and it is a different one.\n   `usql` reads `v$instance`, which an ordinary user cannot see, so it prints\n   a version for an administrator and none for anybody else. Comparing\n   printed output never found that. `docs/USQL.md` holds the measurement.\n   `TestEveryModelIsInTheVersionTable` fails when a model has no row. See D38.\n   Every driver in the `test` module is the one the `dburl` registry names for\n   that dialect: `Scheme.GoPackage` of each scheme whose `Dialect` it is, from\n   dburl v0.29.0, with `Scheme.RequiresCGO` saying whether it needs a C\n   compiler. dburl is upstream of `dbmeta` and of `usql` both, and `usql` is\n   downstream, so its imports do not decide this. The version is the test\n   module's own, because the registry carries none. Where two schemes of one\n   dialect name two packages, test both as subtests named for the driver:\n   SQLite runs on `mattn/go-sqlite3` and `modernc.org/sqlite`, and PostgreSQL\n   on `jackc/pgx/v5/stdlib` and `lib/pq`. Parity is the one exception, and\n   D52 says why. The `go-ora/v3` release that dburl names panics rather than\n   connecting on 11g and 18c, and the fix is upstream and untagged. So the\n   Oracle tests pin v3 at the commit that fixes it, and move to the tag when\n   one holds the fix (D59, D157). If what `usql` imports differs from\n   what dburl names, the fault is in one of those two, and the fix goes\n   there. A query that works here and fails on the driver dburl names is a\n   query that does not work. See D52, D80 and D154.\n11. Never write a `//go:build` constraint on an operating system or an\n   architecture, and never branch on either. Testing is `linux/amd64` only.\n   The same database version is assumed to answer the same way everywhere.\n   A driver can carry its own platform builds, which is the driver's business.\n12. Write idiomatic Go. This code is a move of an older package, so a pattern\n   being present in the source is not a reason to keep it. See D18 in\n   `docs/decisions/` for the two patterns that must not carry over.\n13. `dbmeta` supplies the data and the consumer decides what to show. Never\n   withhold a fact, reorder one, or shape a result so that somebody's output\n   looks right. `usql` filters to match `psql` and `dbtpl` shows none of it,\n   and one set of queries serves both because no presentation is baked in.\n   A field can be added when one statement can produce it: a column already in\n   the row, a column reached by a join, or a correlated subquery whose plan\n   stays bounded as the catalog grows. It cannot when it needs a second\n   statement, a per row round trip, or a scan that grows with the whole\n   catalog. Check a new one with `EXPLAIN` against a catalog with thousands of\n   tables, not against a fixture with five.\n   A child of an object is its own kind with flat rows, never a slice on the\n   parent, because filling a slice needs a second statement or a dialect\n   specific aggregate. See D47.\n14. A new dialect is not finished until you ask several AI models\n   about the queries it cannot answer. `docs/DIALECT.md` holds every step of\n   adding one, and this is one of them. Consult at least two of Gemini,\n   DeepSeek and Astra, and ask each one to sort the unanswered queries into\n   three groups: absent from the product, present under another name, and\n   derivable from several catalog reads or one complex statement. A first pass\n   only finds the objects that the product names the way PostgreSQL names\n   them, and MariaDB proved the cost: 29 queries looked unanswerable until a\n   second opinion named the tables that hold four of them. Treat every answer\n   as a lead and run it against a real server. Leave an analogue that is a\n   stretch unsupported, and record it in `docs/COVERAGE.md` with the reason. See\n   D43.\n15. Never start a container by hand. `test/cmd/dbrun` starts every database\n   this project uses, and every container is named `<product>-<release>`, such\n   as `postgres-18` or `clickhouse-26.9`. Run\n   `cd test && go run ./cmd/dbrun help`, which lists every command, and\n   `docs/DBRUN.md` holds the same table, and the rules for sharing this\n   machine with other sessions. It takes `all` or a tier name\n   where it takes a server. The commands most often wanted are `start`,\n   `stop`, `remove`, `status`, `version`, `dsn`, `usql` and `test`. The rest\n   are in that table and this file does not repeat them.\n   This is not tidiness. A container started by hand gets a port somebody\n   typed rather than the one `container.All` assigns, so `dbrun version`\n   cannot reach a server that is plainly running, and two copies of the same\n   release end up on the machine with nothing to tell them apart. See D68.\n   If you are a coding agent, set `DBMETA_OWNER_NAME` to the name of your\n   session on every `dbrun` command, so that `status` shows whose each server\n   is. See D115.\n16. A dialect is not finished until you ask every query as the\n   administrator and as every lesser kind of principal the product has, and\n   the differences are written down. Add the principals to `parityTargets` in\n   `test/parity_test.go`, run `go test -run TestPrivilegeParity -update`, and\n   read the diff.\n   A principal is not one thing. SQL Server has three: a sysadmin, a server\n   login mapped to a database user, and a contained database user whose\n   password is in the database and which has no login at the server. Oracle\n   has the same three from 12c, where a common user is the login and a local\n   user in a pluggable database is the contained user. PostgreSQL and MySQL\n   have no containment, so each has a superuser or root, an owner, and a\n   grantee. SQLite and DuckDB have no user at all and the rule cannot reach\n   them.\n   Parity ships with the queries, the fixture and the documentation. They are\n   one deliverable. `TestEveryDialectIsMeasuredForParity` fails when a dialect\n   has neither a target nor an entry in `parityExempt` saying why it has none,\n   because `TestPrivilegeParity` says nothing about a target nobody wrote.\n   A scene the server is too old for carries a `min` and is skipped with the\n   reason, the way a fixture step is.\n   This is not a formality. It found six MariaDB queries that are refused\n   outright for a user holding ALL PRIVILEGES on its own database, because\n   they read tables in the `mysql` database rather than views that filter\n   themselves. Nothing had recorded that. See D61.\n\n## Layout\n\n- `/` is the root package `dbmeta`. It holds the driver agnostic API: the\n  object types, the `Query` values, the one method `Queryer` interface, the\n  `Args` filter, and the error values. External projects use this package.\n- `models/<driver>` holds one model, written by hand. One package covers every\n  supported version of that database. For example, `models/sqlite3`. A model\n  registers its queries from `init` and nothing generates it, so edit it\n  directly. See D71.\n- `all/` holds one file per model, such as `all/postgres.go`, each a blank\n  import gated by a build tag. Importing `all` registers the base set, and\n  `no_base`, a model's own name and `no_<model>` change which. A model can be\n  imported from `models/<driver>` instead. Say model, not driver: `dbmeta`\n  never opens a connection. See D31.\n- `container/` names every database release the tests run against, as Go data.\n  It starts no container and imports no container client. `test/cmd/dbrun` and\n  the CI workflow both read it, and a test fails when they drift.\n- `hosted/` names the hosted services, such as Snowflake and BigQuery, as Go\n  data. It holds no secret. `dbrun` resolves each one's connection string at\n  run time, and a service exists in `dbrun` only while it resolves. See D117.\n- `test/cmd/dbrun` is the only thing that starts a database, container or\n  virtual machine alike. `container/machine.go` holds the machine list, and\n  `container/windows.go` holds the Windows machines in it. `dbrun provision`\n  builds a Windows machine from the payload it embeds in\n  `test/cmd/dbrun/oem/`, and imports an appliance from the file a person\n  downloaded. Read `docs/WINDOWS.md`. See D57, D68 and D86.\n- `test/cmd/dbrun/image/` holds the Containerfiles this repository builds,\n  one for each product whose image dbrun builds. `dbrun` embeds them all and\n  builds `<product>.Containerfile` as `localhost/dbmeta/<product>`. See D118.\n  The Apache Cassandra image cannot be configured from the outside for what\n  the queries read, so the settings are baked in. `dbrun` embeds the file and\n  builds the image when it is missing.\n- `test/` is a separate module with its own `go.mod`. It holds the integration\n  tests and the database drivers. Neither can appear in the root module. It is\n  the only place cgo is allowed, and the separate `go.mod` is what makes that\n  safe.\n\nRead an existing model before you write one. `models/postgres` is the primary\none and `models/sqlite3` is a small one.\n\n## Writing a model\n\nNothing generates the code here. Every model is written, and no tool produces\nor reproduces it, so a file under `models/` is edited in place. See D71.\n\nA model is one package that registers a `Binding` per object kind from `init`.\nThe `Binding` carries the statement as `Stmt`, the `Fields` it returns, the\n`Params` it takes and a `Scan` function. A query that must differ between\nreleases is one statement with version fragments, which is rule 3.\n\nWrite the statement against a running server rather than from the\ndocumentation. `dbrun` is the only thing that starts one:\n\n```bash\ncd test && go run ./cmd/dbrun start postgres && go run ./cmd/dbrun usql postgres\n```\n\n`dbrun start` leaves the server up, which is what writing a query needs.\nRemove it with `dbrun remove postgres` when you are done. See D68.\n\nTwo rules catch the recurring NULL scan fault. Give a field `sql.Null[T]`\nwhenever the catalog column can be NULL, and select `NULL AS \"name\"` rather\nthan a literal where a release has no source for a column. A column that the\ncatalog declares NOT NULL can still arrive NULL through an outer join, so read\n`docs/NULLS.md` before deciding.\n\nA model is not finished until it has run against every release in its tier,\nits fixture builds one of every object its queries read, and its parity\ntargets exist. Those are one deliverable. See rule 9, rule 16 and D61.\n\n## Go conventions\n\nWrap every error with `%w`, never `%s` or `%v`:\n\n```go\nif err != nil {\n    return nil, fmt.Errorf(\"reading columns for %s: %w\", table, err)\n}\n```\n\nWrite error messages in lower case, starting with a gerund. Do not write\n\"failed to\" or \"error\". Name the object that failed.\n\nNever hide a NULL. Read `docs/NULLS.md` before writing a query for any database. It\nis the shortest document here and it is the one that has cost the most to\nlearn.\n\nIn brief: never wrap a nullable catalog column in `COALESCE`, and never pad an\nabsent column with a literal. Give the field the type `sql.Null[string]`, or\n`sql.Null[T]` for whatever T it is, and select `NULL AS \"name\"`. There is no\nalias for a nullable type and there must not be one: a reader of\n`go doc dbmeta.Sequence` learns from the field itself that it can be absent,\nand does not learn it from a name like `Text`. See D51. Before padding, ask whether the value on the old release is\nunknown or genuinely that value, and set `Field.Min` only for the first.\n\nDeclare sentinel errors as constants of a defined string type, never as\nvariables. A `var` declared with `errors.New` can be reassigned by any\nimporter. A constant cannot.\n\n```go\n// Error is an error.\ntype Error string\n\n// Error satisfies the error interface.\nfunc (err Error) Error() string {\n\treturn string(err)\n}\n\n// Error values.\nconst (\n\t// ErrNotSupported is the not supported error.\n\tErrNotSupported Error = \"not supported\"\n)\n```\n\nKeep the `Err` prefix and the PascalCase name. Compare with `errors.Is`.\n\nAccept interfaces and return concrete structs. Keep an interface to three\nmethods or fewer. Define an interface where it is consumed, not where it is\nimplemented.\n\nDo not name a type `Reader`. Everything in this module reads, so the word adds\nnothing and repeats the package.\n\nReturn an iterator, `iter.Seq2[Table, error]`. Read the result one record at a\ntime and never materialize it into a slice. Do not write a cursor type. An\nerror can arrive part way through a result, so a caller must be able to tell\nthe end of a result from a failure in the middle of one.\n\nAn iterator holds a database connection until it ends. Stopping early, by\n`break` or by `yield` returning false, must close the rows and release the\nconnection, and so must canceling the context. A caller that nests one\niterator inside another uses a second connection, which deadlocks on a pool of\none. Document the filter form that avoids nesting.\n\nUse generics rather than `interface{}` for a container of one type. Compare\nerrors with `errors.Is` and `errors.As`, never by string.\n\nPut `context.Context` first in every parameter list and name it `ctx`. Never\nstore it in a struct.\n\nName a package with one short lower case word. Do not stutter: write\n`postgres.Reader`, not `postgres.PostgresReader`.\n\nName a receiver with one or two lower case letters, and use the same name on\nevery method of the type. Never write `this` or `self`.\n\nGroup struct fields by purpose, with exported fields first and a blank line\nbetween groups.\n\n## Linting\n\n`.golangci.yml` holds the rules, and `test/.golangci.yml` holds them for the\ntest module. golangci-lint reads the `go.mod` of the directory it runs in, so\nboth are linted separately and CI runs it twice.\n\n```bash\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\nThe posture is `default: all` with a disable list. A new linter gets a look\nrather than silence, and that has paid: turning it on found a missing\n`rows.Err` check, a parameter shadowing the `min` builtin, and a copy loop that\n`maps.Copy` now does.\n\n### The rule for a lint finding\n\nA linter that makes idiomatic Go worse is disabled, with the reason written\nbeside it in the configuration. Only a real defect gets a code change. Ask\nwhich one it is before you touch the code, and if the remedy reads worse than\nwhat it replaces, that is the answer.\n\nA change made to quiet a linter rather than to fix something is itself a\ndefect. It leaves code that no reader can explain and that the next person\ncopies. Two were written here and reverted, and they are named so that they do\nnot come back:\n\nNever write `defer func() { _ = rows.Close() }()`. Write `defer rows.Close()`.\nA deferred `Close` returns an error nothing can act on, and the error that\nmatters was already reported by `Err`. `errcheck` is configured to allow it.\n\nNever add an empty `case X:` to satisfy `exhaustive`. A switch that handles two\nof three values and answers the third after the switch says more than a case\nwith no body. `exhaustive` is disabled.\n\nA guard that can never fire is not a fix. `gosec` wanted a bound on a\nconversion of a PostgreSQL `server_version_num`, which is six digits, and the\nguard first written for it tested the wrong quantity and was never able to\nfire. That warning is excluded with its reason.\n\nRead `golangci-lint run` output as a list of questions, not a list of tasks.\n\n## Container images\n\n`container/` names every server release that `dbrun` starts, as Go data, one\nfile per product, and `container.All` in `container/container.go` joins them.\nMost are what dbmeta is tested against. The rest are Staged, because no model\nreads them: they are there for dbimp's drivers, for the flavors usql reaches,\nand for the emulators of hosted services, and CI does not run them (D118,\nD119). The package starts\nnothing and imports no container client, and it must not: a consumer brings\nits own podman, docker or Go client and picks its own version of it, the same\nway it brings its own driver.\n\nAn embedded database is not in there and must not be. SQLite3, DuckDB,\nmoderncsqlite, chai and csvq have no server and no container, and the\nrelease is whichever one the pinned Go driver ships. `dbrun` knows them\nitself (D116), and CI tests the ones a model reads in the same matrix as the\nservers, where `dbrun` starts nothing for them. chai and csvq have no\nmodel yet, so they are Staged (D119). See D42.\n\nThat list is the only copy. `test/cmd/dbrun` reads it, the CI workflow builds\nits matrix from `dbrun list --json --names`, and\n`container/workflow_test.go` fails when the workflow and the Go list disagree.\nChange the product's file in `container/` first and the workflow second.\n\nAdd a release there before you add it anywhere else.\n\n## Before you commit\n\nRun these:\n\n```bash\ngofmt -l . && go vet ./... && go build ./... && go test -race -count=2 ./...\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\n`gofmt -l .` must print nothing.\n\nRun it with `-count=2`, because that is what CI runs and a weaker local command\nhas already let two failures through. Both were the same fault: a test body\nthat registers a dialect or a query, which a registry rejects the second time.\nRegistration belongs in `init`.\n\nThat does not cover the `test` module. `./...` does not descend into a\ndirectory that has its own `go.mod`, so run the integration tests separately:\n\n```bash\ncd test && go test ./...\n```\n\nA test in the root module never opens a database connection. Test fragment\nmerging, version parsing and model selection there, against golden files. A\ntest that needs a server belongs in the `test` module.\n\nCI runs on GitHub Actions, on `ubuntu-latest` only. The queries are SQL and the\ncode is pure Go, so `dbmeta` does not need the runner matrix that the other\n`xo` projects use. Keep the workflow small.\n\nCI runs a release matrix, which D42 decided and which replaced the single\nlatest version D24 first called for. Every push runs the Tested tier, and\n`container.AtTier(container.Tested)` is the list. This file does not repeat\nit, because the copy written here went stale the moment a product was added,\nand it named six products for as long as ClickHouse, Trino, Presto, Firebird,\nSAP HANA and Apache Hive went on being added to the tier without it. Read the\nlist with:\n\n```bash\ncd test && go run ./cmd/dbrun list --json --names tested\n```\n\nThat command adds the embedded databases that a model reads, which are not\nin `container.AtTier` at all, and `dbrun` starts nothing for them. D42 keeps an\nembedded database out of the release list, and `dbrun` puts them back because\na person asking for the tier wants to run them too. The Nightly tier runs\nonce a night.\n\n`dbrun` builds the Cassandra image before it tests a Cassandra release, from\nthe Containerfile it embeds, because the published image refuses what the\nqueries read. See D62 and `test/cmd/dbrun/image/cassandra.Containerfile`.\n\nDo not write that list in the workflow from memory. It lives in `container/`,\n`container.AtTier` selects a tier, and\n`TestWorkflowReadsTheList` fails when the YAML and the Go disagree. Change the\nGo first and then the YAML. See D42 and D54.\n\nCI uses no database that is preinstalled on the runner. `dbrun` starts every\nrelease, and the compare job runs MariaDB 13.0 and MySQL 26.7 as service\ncontainers to check that the two products answer the same way (D44).\n\n## Writing documentation\n\nA new document goes in `docs/`. Only `README.md`, `AGENTS.md`, `CLAUDE.md` and\n`CONTRIBUTING.md` belong in the repository root, and a test enforces that. Add\nit to the table at the top of this file and to the one in `README.md`, because\na document nobody can find is a document nobody reads. See D50.\n\nA decision goes in a file of its own in `docs/decisions/` and nowhere else\n(D111). Name it `D<nnn>-<title>.md` with the next number. It opens with\n`# D<n>. <title>`, a blank line, and `Status: <status>.`, where the status is\n`Decided`, or `Amends D26`, or `Superseded by D24`. Add its row to\n`docs/decisions/README.md`. `TestTheDecisionIndexIsComplete` prints the row\nwhen it is missing. If your decision changes an earlier one, say so in both\nstatuses, because a reader who finds the older one has to be told.\n\nWrite plain English. Use short sentences and the active voice. Use `can`,\n`will`, and `must`, and do not use `should`, `may`, or `might`. Do not use\nsemicolons or em dashes. Put the condition before the command: \"If the query\nreturns no rows, return the empty set.\"\n\nLoad the `simple-english` skill before you write any text that a person reads:\na document, a code comment, an error message or a commit message. Follow it\nfor that text. Its rules include the ones above and add more, such as no\ncontractions and one word for one meaning. Ken asked for this. See D89 and\nD110.\n\n`TestProseIsSimpleEnglish` checks the rules of the skill that a machine can\ncheck: the banned modals, semicolons, dashes, contractions, bold, \"has been\",\nBritish spellings, filler words and Latin abbreviations. It reads every\ndocument, every comment in Go, YAML, a Containerfile, a batch file or an INI\nfile, and every error and test message. It skips text\nin backticks or double quotes. There is no marker that turns a rule off. If\nit reports a sentence, rewrite the sentence. Sentence length and the voice\nare still yours to check. See D156.\n\nWork that is known and not done goes in `docs/BACKLOG.md`, with the decision\nor the measurement that found it. When an item is done, delete it, and record\nanything decided in `docs/decisions/`.\n","CLAUDE.md":"@AGENTS.md\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# dbmeta\n\n`dbmeta` holds database metadata queries and models in one Go module. `usql`\nand `dbtpl` consume it.\n\n`dbmeta` only reads. It does not render metadata and it does not write it. The\n`Reader` and `Writer` pair from `usql` is a `usql` concept and it does not\ncome here.\n\nOne statement is the exception and it is a narrow one. `Dialect.ChangePassword`\nbuilds the statement that sets a password and returns it as text, because the\nstatement and its escaping are per product knowledge and a password cannot be\nbound as a parameter. It takes no database and runs nothing, so everything\n`dbmeta` executes is still a read. Do not widen that. See D56.\n\nEvery quoting rule lives here, and none in a consumer. A rule two products\nshare goes in the root package, as `QuoteLiteral` and `QuoteIdentifier` do,\nand a product's own goes beside its model. See D127.\n\n## Standing rules\n\nThese hold in every `xo` repository, for every coding agent (D110).\n\n1. Stage changes for review. Commit and push only when Ken says so.\n2. Load the `simple-english` skill before you write any text that a person\n   reads: a document, a code comment, an error message or a commit message.\n   Follow it for that text.\n3. Load the `go-pedantry` skill before you write or review Go code. Follow it\n   where it does not conflict with a rule in this file. A rule here wins.\n\n`CLAUDE.md` holds one line that imports this file, so that Claude Code and\nevery other agent read the same rules. Edit this file, not that one.\n\n## Which document to read\n\nTwo before anything else. `docs/NULLS.md` is the shortest and the one that cost\nthe most to learn. `docs/decisions/` holds every decision, one file each, and\n`docs/decisions/README.md` is a table of all 246. Read the status, because 103\nof them amend or replace an earlier one. Do not decide an open question on\nyour own. They are at the end of `docs/PLAN.md`. Ask Ken.\n\nThen by what you are doing:\n\n| If you are | Read |\n| --- | --- |\n| writing or changing any query | `docs/NULLS.md`, then `docs/COVERAGE.md` |\n| asking why something is the way it is | the index in `docs/decisions/README.md` |\n| adding an object kind | D46 and D47 in `docs/decisions/`, then `docs/COMMANDS.md` |\n| adding a database | `docs/DIALECT.md`, which is every step in order. It points at D66, `docs/EVALUATION.md`, D43, D154, D38 and D61 |\n| wondering which database comes next | D66, which holds the order and why a product with no container is last |\n| a parity failure | D61. Decide whether the query began depending on who is asking, or whether one release genuinely differs, and record it |\n| changing what a database answers | `docs/COVERAGE.md`, which is the record of what each one can do |\n| changing a fixture | D53, then run the fixture test in the root module. A core object belongs in every fixture |\n| a conformance failure | D53. Decide whether it is a fault or a product difference, then fix it or rewrite the expectation and say why |\n| deciding whether a field belongs here | D47 in `docs/decisions/`, which holds the cost test |\n| wiring up a client | `docs/COMMANDS.md`, then `docs/USQL.md` or `docs/DBTPL.md` |\n| designing the object set | `docs/QUERIES.md`, the survey of psql against information_schema |\n| adding a release to CI | the product's file in `container/`, which is the only copy of its list |\n| adding an old SQL Server that needs a Windows VM | `docs/DIALECT.md` for where the steps differ, then `docs/WINDOWS.md`, `container/windows.go` and D57 |\n| starting a database for any reason | `dbrun`, and nothing else. `docs/DBRUN.md` holds its use and the rules for a shared machine. Read those rules first |\n| adding a container or a machine that dbrun starts | `docs/CONTAINERS.md`, every step in order |\n| reaching a hosted service, such as Snowflake | `docs/DBRUN.md`, under Hosted services, and D117. Never enter a credential yourself |\n| running a product that needs a license file, such as Stardog | `docs/DBRUN.md`, under License files, and D118. Never sign up for one or download one yourself |\n| adding an entry for a product dbimp or usql reaches | D118, which holds the rules the entries follow and what each one cost to measure |\n| changing how a database is started | D68, D70, D75, D86, D97, D98, D105, D108 and D115 in `docs/decisions/`, then `docs/DBRUN.md` |\n| changing how dbrun calls podman or docker | D122. Read JSON, name many containers in one call, and do not ask twice |\n| provisioning a Windows machine | `docs/WINDOWS.md`, then `container/windows.go` and D57 |\n| testing against Cassandra | `test/cmd/dbrun/image/cassandra.Containerfile`. `dbrun` embeds it and builds it when the image is missing |\n| changing the CI workflow | D69. It reads the release list from `dbrun list --json` and names no image of its own |\n| answering a lint finding | the rule below, under Linting |\n| ignoring a build artifact | the root `.gitignore`, which is the only one. See D58 |\n| writing a document, a code comment, an error message or a commit message | the `simple-english` skill. Load it first. See below, under Writing documentation |\n| writing or reviewing Go code | the `go-pedantry` skill. Load it first. See Standing rules |\n| looking for work that is known and not done | `docs/BACKLOG.md` |\n| resuming a session that ended or crashed | `docs/PROGRESS.md`, which says where the work stands |\n| adding or updating an agent skill | `CONTRIBUTING.md`, under Agent skills, D89 and D110 |\n\n`CONTRIBUTING.md` is the same thing for a person, and shorter.\n\nA document that is not in that table does not exist. If you cannot find where\nsomething is written down, it is not written down, and it is an open question.\n\n## Hard rules\n\n1. The root module depends on the standard library and on nothing else. Its\n   `go.mod` has no `require` block and must keep it that way. Never add a\n   database driver, not even for a test. A consumer brings its own driver and\n   picks its own version of it, and `dbmeta` must not constrain that. Anything\n   needing a driver goes in the `test` module. Do not add any third party\n   package without asking Ken first.\n   D19 once made `github.com/xo/dburl` a direct dependency. It never became\n   one, because nothing here opens a connection: the caller passes a `DB` and\n   names a `Dialect`, so there is no URL to parse. That is the better answer\n   and it stands.\n   The rule that outlived the dependency is this one. Never write a scheme\n   list, an alias list, a flavor table, or a connection string parser in this\n   module. That taxonomy is `dburl`'s and repeating it is how two copies start\n   disagreeing. A consumer that has a URL reads `dburl` itself:\n   `URL.Dialect`, from dburl v0.32.0, is the dbmeta `Dialect` and selects the\n   model package, and `URL.OriginalScheme` holds an alias such as `mariadb`.\n   A product that speaks another product's protocol, such as `cockroachdb`\n   or `tidb`, has a `Dialect` of its own from dburl v0.36.0, so nothing else\n   names it. `URL.Driver` is the name `sql.Open` takes and never the dialect:\n   `pgx://` and `postgres://` both have the dialect `postgres`. See D99 and\n   D125.\n2. PostgreSQL is the primary model, and `psql` defines it. When two databases\n   describe the same object differently, follow `psql`. This decides the shape\n   of an answer. It does not require every database to answer every question.\n   `psql` sets the object model. It does not set the column set, and it never\n   did: `psql` prints what a person reads at a terminal, and a consumer needs\n   the parts. Return a fact `psql` does not print when one statement can\n   produce it, and never return prose as the only form of anything. D47 holds\n   the cost test, and rule 13 is the short version.\n3. A query that must differ between releases is one query with version\n   fragments, held as data in the one model package. There is no\n   package per release. A fragment must never change the set of columns a\n   query returns. Whichever side lacks a source pads, old or new: select the\n   column as `NULL AS name` on the server that has no source for it. A type\n   that differs between versions is cast to a common one, never left to `any`.\n   Where two products share a dialect, a fragment for one of them gates on\n   that product's version key and never on the number alone. MariaDB 11.8 and\n   MySQL 9 have no numeric relation, so a gate at `V(10, 2)` silently means\n   \"MariaDB only\" and shipped a wrong answer once already. Write\n   `Gate{Key: MariaDB, Min: V(10, 2)}`. Set a key only for a product you\n   detected. See D44.\n4. Stay backward compatible within reason. An old database keeps working when\n   support for a new one arrives. Every version sits in one of five tiers:\n   Tested in CI on every push, Nightly in CI once a night, Verified on a\n   development machine before a release and never in CI, Staged when no\n   model reads it yet, or Archived with no tests at all. Never call a version\n   supported without naming its tier, and never call a Staged one supported\n   at all. D40 set three, D42 added Nightly and D119 added Staged. The first\n   four are values of `container.Tier` and Archived is not, because an\n   archived release is one `container.All` does not name. A release is Staged\n   exactly when no model reads it. A Staged release records the cadence it\n   will have, Tested, Nightly or Verified, and takes it as its tier in the\n   change that adds its model. See D40, D42, D119 and D120.\n5. A query translated from a source tree that upstream no longer ships records\n   the release and commit of that tree beside the query. PostgreSQL 9.6 is the\n   case: `psql` dropped it in release 20, so there is nothing current to check\n   a translation against.\n6. Configuration is a value that the caller owns. Do not put a configured\n   value in a package level variable. One driver borrowed another driver's\n   configuration in `usql` and shipped a fault to users.\n7. Do not import `usql` or `dbtpl`. The dependency runs the other way.\n8. Every function that reads metadata takes `ctx context.Context` as its first\n   parameter. There are no exceptions. Call `QueryContext` and its relatives,\n   never `Query`, `Exec`, `QueryRow`, or `Prepare`. Never call\n   `context.Background()` or `context.TODO()` inside this library.\n9. Every model ships both its queries and its fixtures. The fixture creates one\n   of every object the queries read, so a test gets rows worth checking. It is\n   versioned like the queries, because syntax and objects arrive in different\n   releases, and a step the server is too old for is skipped rather than\n   refused. A query that has never run against a real server is not finished.\n10. The root module is pure Go and has no cgo, ever. It has no driver at all,\n   so there is nothing to argue about: a consumer builds it with\n   `CGO_ENABLED=0` and cross compiles it.\n   The `test` module can use cgo, because its own `go.mod` keeps it out of\n   everything a consumer builds. Where a database has a canonical cgo driver\n   and a pure Go one, test both: the canonical driver is what users run, and\n   the pure Go one is what a consumer who cannot use cgo runs, and the two\n   ship different library versions. Two drivers need cgo today, and only two:\n   `mattn/go-sqlite3`, which builds the real SQLite source and is the primary\n   SQLite driver, and `duckdb/duckdb-go`.\n   Never `godror`, which needs Oracle client libraries rather than only a C\n   compiler. See D48.\n   Compare the version query too. Adding or changing a dialect means reading\n   what `usql` runs for the same product, in the `Version` field of its\n   `drivers.Driver`, and recording the comparison in the statements table in\n   `docs/USQL.md`. A driver declaring no `Version` falls through to the generic\n   `SELECT version();`, and that counts as its statement. Read the field and\n   not the `drivers.Register` call: Oracle and godror both register through\n   `orshared.Register`, so a grep for the call says neither has a version\n   query and both do. The Oracle defect is real and it is a different one.\n   `usql` reads `v$instance`, which an ordinary user cannot see, so it prints\n   a version for an administrator and none for anybody else. Comparing\n   printed output never found that. `docs/USQL.md` holds the measurement.\n   `TestEveryModelIsInTheVersionTable` fails when a model has no row. See D38.\n   Every driver in the `test` module is the one the `dburl` registry names for\n   that dialect: `Scheme.GoPackage` of each scheme whose `Dialect` it is, from\n   dburl v0.29.0, with `Scheme.RequiresCGO` saying whether it needs a C\n   compiler. dburl is upstream of `dbmeta` and of `usql` both, and `usql` is\n   downstream, so its imports do not decide this. The version is the test\n   module's own, because the registry carries none. Where two schemes of one\n   dialect name two packages, test both as subtests named for the driver:\n   SQLite runs on `mattn/go-sqlite3` and `modernc.org/sqlite`, and PostgreSQL\n   on `jackc/pgx/v5/stdlib` and `lib/pq`. Parity is the one exception, and\n   D52 says why. The `go-ora/v3` release that dburl names panics rather than\n   connecting on 11g and 18c, and the fix is upstream and untagged. So the\n   Oracle tests pin v3 at the commit that fixes it, and move to the tag when\n   one holds the fix (D59, D157). If what `usql` imports differs from\n   what dburl names, the fault is in one of those two, and the fix goes\n   there. A query that works here and fails on the driver dburl names is a\n   query that does not work. See D52, D80 and D154.\n11. Never write a `//go:build` constraint on an operating system or an\n   architecture, and never branch on either. Testing is `linux/amd64` only.\n   The same database version is assumed to answer the same way everywhere.\n   A driver can carry its own platform builds, which is the driver's business.\n12. Write idiomatic Go. This code is a move of an older package, so a pattern\n   being present in the source is not a reason to keep it. See D18 in\n   `docs/decisions/` for the two patterns that must not carry over.\n13. `dbmeta` supplies the data and the consumer decides what to show. Never\n   withhold a fact, reorder one, or shape a result so that somebody's output\n   looks right. `usql` filters to match `psql` and `dbtpl` shows none of it,\n   and one set of queries serves both because no presentation is baked in.\n   A field can be added when one statement can produce it: a column already in\n   the row, a column reached by a join, or a correlated subquery whose plan\n   stays bounded as the catalog grows. It cannot when it needs a second\n   statement, a per row round trip, or a scan that grows with the whole\n   catalog. Check a new one with `EXPLAIN` against a catalog with thousands of\n   tables, not against a fixture with five.\n   A child of an object is its own kind with flat rows, never a slice on the\n   parent, because filling a slice needs a second statement or a dialect\n   specific aggregate. See D47.\n14. A new dialect is not finished until you ask several AI models\n   about the queries it cannot answer. `docs/DIALECT.md` holds every step of\n   adding one, and this is one of them. Consult at least two of Gemini,\n   DeepSeek and Astra, and ask each one to sort the unanswered queries into\n   three groups: absent from the product, present under another name, and\n   derivable from several catalog reads or one complex statement. A first pass\n   only finds the objects that the product names the way PostgreSQL names\n   them, and MariaDB proved the cost: 29 queries looked unanswerable until a\n   second opinion named the tables that hold four of them. Treat every answer\n   as a lead and run it against a real server. Leave an analogue that is a\n   stretch unsupported, and record it in `docs/COVERAGE.md` with the reason. See\n   D43.\n15. Never start a container by hand. `test/cmd/dbrun` starts every database\n   this project uses, and every container is named `<product>-<release>`, such\n   as `postgres-18` or `clickhouse-26.9`. Run\n   `cd test && go run ./cmd/dbrun help`, which lists every command, and\n   `docs/DBRUN.md` holds the same table, and the rules for sharing this\n   machine with other sessions. It takes `all` or a tier name\n   where it takes a server. The commands most often wanted are `start`,\n   `stop`, `remove`, `status`, `version`, `dsn`, `usql` and `test`. The rest\n   are in that table and this file does not repeat them.\n   This is not tidiness. A container started by hand gets a port somebody\n   typed rather than the one `container.All` assigns, so `dbrun version`\n   cannot reach a server that is plainly running, and two copies of the same\n   release end up on the machine with nothing to tell them apart. See D68.\n   If you are a coding agent, set `DBMETA_OWNER_NAME` to the name of your\n   session on every `dbrun` command, so that `status` shows whose each server\n   is. See D115.\n16. A dialect is not finished until you ask every query as the\n   administrator and as every lesser kind of principal the product has, and\n   the differences are written down. Add the principals to `parityTargets` in\n   `test/parity_test.go`, run `go test -run TestPrivilegeParity -update`, and\n   read the diff.\n   A principal is not one thing. SQL Server has three: a sysadmin, a server\n   login mapped to a database user, and a contained database user whose\n   password is in the database and which has no login at the server. Oracle\n   has the same three from 12c, where a common user is the login and a local\n   user in a pluggable database is the contained user. PostgreSQL and MySQL\n   have no containment, so each has a superuser or root, an owner, and a\n   grantee. SQLite and DuckDB have no user at all and the rule cannot reach\n   them.\n   Parity ships with the queries, the fixture and the documentation. They are\n   one deliverable. `TestEveryDialectIsMeasuredForParity` fails when a dialect\n   has neither a target nor an entry in `parityExempt` saying why it has none,\n   because `TestPrivilegeParity` says nothing about a target nobody wrote.\n   A scene the server is too old for carries a `min` and is skipped with the\n   reason, the way a fixture step is.\n   This is not a formality. It found six MariaDB queries that are refused\n   outright for a user holding ALL PRIVILEGES on its own database, because\n   they read tables in the `mysql` database rather than views that filter\n   themselves. Nothing had recorded that. See D61.\n\n## Layout\n\n- `/` is the root package `dbmeta`. It holds the driver agnostic API: the\n  object types, the `Query` values, the one method `Queryer` interface, the\n  `Args` filter, and the error values. External projects use this package.\n- `models/<driver>` holds one model, written by hand. One package covers every\n  supported version of that database. For example, `models/sqlite3`. A model\n  registers its queries from `init` and nothing generates it, so edit it\n  directly. See D71.\n- `all/` holds one file per model, such as `all/postgres.go`, each a blank\n  import gated by a build tag. Importing `all` registers the base set, and\n  `no_base`, a model's own name and `no_<model>` change which. A model can be\n  imported from `models/<driver>` instead. Say model, not driver: `dbmeta`\n  never opens a connection. See D31.\n- `container/` names every database release the tests run against, as Go data.\n  It starts no container and imports no container client. `test/cmd/dbrun` and\n  the CI workflow both read it, and a test fails when they drift.\n- `hosted/` names the hosted services, such as Snowflake and BigQuery, as Go\n  data. It holds no secret. `dbrun` resolves each one's connection string at\n  run time, and a service exists in `dbrun` only while it resolves. See D117.\n- `test/cmd/dbrun` is the only thing that starts a database, container or\n  virtual machine alike. `container/machine.go` holds the machine list, and\n  `container/windows.go` holds the Windows machines in it. `dbrun provision`\n  builds a Windows machine from the payload it embeds in\n  `test/cmd/dbrun/oem/`, and imports an appliance from the file a person\n  downloaded. Read `docs/WINDOWS.md`. See D57, D68 and D86.\n- `test/cmd/dbrun/image/` holds the Containerfiles this repository builds,\n  one for each product whose image dbrun builds. `dbrun` embeds them all and\n  builds `<product>.Containerfile` as `localhost/dbmeta/<product>`. See D118.\n  The Apache Cassandra image cannot be configured from the outside for what\n  the queries read, so the settings are baked in. `dbrun` embeds the file and\n  builds the image when it is missing.\n- `test/` is a separate module with its own `go.mod`. It holds the integration\n  tests and the database drivers. Neither can appear in the root module. It is\n  the only place cgo is allowed, and the separate `go.mod` is what makes that\n  safe.\n\nRead an existing model before you write one. `models/postgres` is the primary\none and `models/sqlite3` is a small one.\n\n## Writing a model\n\nNothing generates the code here. Every model is written, and no tool produces\nor reproduces it, so a file under `models/` is edited in place. See D71.\n\nA model is one package that registers a `Binding` per object kind from `init`.\nThe `Binding` carries the statement as `Stmt`, the `Fields` it returns, the\n`Params` it takes and a `Scan` function. A query that must differ between\nreleases is one statement with version fragments, which is rule 3.\n\nWrite the statement against a running server rather than from the\ndocumentation. `dbrun` is the only thing that starts one:\n\n```bash\ncd test && go run ./cmd/dbrun start postgres && go run ./cmd/dbrun usql postgres\n```\n\n`dbrun start` leaves the server up, which is what writing a query needs.\nRemove it with `dbrun remove postgres` when you are done. See D68.\n\nTwo rules catch the recurring NULL scan fault. Give a field `sql.Null[T]`\nwhenever the catalog column can be NULL, and select `NULL AS \"name\"` rather\nthan a literal where a release has no source for a column. A column that the\ncatalog declares NOT NULL can still arrive NULL through an outer join, so read\n`docs/NULLS.md` before deciding.\n\nA model is not finished until it has run against every release in its tier,\nits fixture builds one of every object its queries read, and its parity\ntargets exist. Those are one deliverable. See rule 9, rule 16 and D61.\n\n## Go conventions\n\nWrap every error with `%w`, never `%s` or `%v`:\n\n```go\nif err != nil {\n    return nil, fmt.Errorf(\"reading columns for %s: %w\", table, err)\n}\n```\n\nWrite error messages in lower case, starting with a gerund. Do not write\n\"failed to\" or \"error\". Name the object that failed.\n\nNever hide a NULL. Read `docs/NULLS.md` before writing a query for any database. It\nis the shortest document here and it is the one that has cost the most to\nlearn.\n\nIn brief: never wrap a nullable catalog column in `COALESCE`, and never pad an\nabsent column with a literal. Give the field the type `sql.Null[string]`, or\n`sql.Null[T]` for whatever T it is, and select `NULL AS \"name\"`. There is no\nalias for a nullable type and there must not be one: a reader of\n`go doc dbmeta.Sequence` learns from the field itself that it can be absent,\nand does not learn it from a name like `Text`. See D51. Before padding, ask whether the value on the old release is\nunknown or genuinely that value, and set `Field.Min` only for the first.\n\nDeclare sentinel errors as constants of a defined string type, never as\nvariables. A `var` declared with `errors.New` can be reassigned by any\nimporter. A constant cannot.\n\n```go\n// Error is an error.\ntype Error string\n\n// Error satisfies the error interface.\nfunc (err Error) Error() string {\n\treturn string(err)\n}\n\n// Error values.\nconst (\n\t// ErrNotSupported is the not supported error.\n\tErrNotSupported Error = \"not supported\"\n)\n```\n\nKeep the `Err` prefix and the PascalCase name. Compare with `errors.Is`.\n\nAccept interfaces and return concrete structs. Keep an interface to three\nmethods or fewer. Define an interface where it is consumed, not where it is\nimplemented.\n\nDo not name a type `Reader`. Everything in this module reads, so the word adds\nnothing and repeats the package.\n\nReturn an iterator, `iter.Seq2[Table, error]`. Read the result one record at a\ntime and never materialize it into a slice. Do not write a cursor type. An\nerror can arrive part way through a result, so a caller must be able to tell\nthe end of a result from a failure in the middle of one.\n\nAn iterator holds a database connection until it ends. Stopping early, by\n`break` or by `yield` returning false, must close the rows and release the\nconnection, and so must canceling the context. A caller that nests one\niterator inside another uses a second connection, which deadlocks on a pool of\none. Document the filter form that avoids nesting.\n\nUse generics rather than `interface{}` for a container of one type. Compare\nerrors with `errors.Is` and `errors.As`, never by string.\n\nPut `context.Context` first in every parameter list and name it `ctx`. Never\nstore it in a struct.\n\nName a package with one short lower case word. Do not stutter: write\n`postgres.Reader`, not `postgres.PostgresReader`.\n\nName a receiver with one or two lower case letters, and use the same name on\nevery method of the type. Never write `this` or `self`.\n\nGroup struct fields by purpose, with exported fields first and a blank line\nbetween groups.\n\n## Linting\n\n`.golangci.yml` holds the rules, and `test/.golangci.yml` holds them for the\ntest module. golangci-lint reads the `go.mod` of the directory it runs in, so\nboth are linted separately and CI runs it twice.\n\n```bash\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\nThe posture is `default: all` with a disable list. A new linter gets a look\nrather than silence, and that has paid: turning it on found a missing\n`rows.Err` check, a parameter shadowing the `min` builtin, and a copy loop that\n`maps.Copy` now does.\n\n### The rule for a lint finding\n\nA linter that makes idiomatic Go worse is disabled, with the reason written\nbeside it in the configuration. Only a real defect gets a code change. Ask\nwhich one it is before you touch the code, and if the remedy reads worse than\nwhat it replaces, that is the answer.\n\nA change made to quiet a linter rather than to fix something is itself a\ndefect. It leaves code that no reader can explain and that the next person\ncopies. Two were written here and reverted, and they are named so that they do\nnot come back:\n\nNever write `defer func() { _ = rows.Close() }()`. Write `defer rows.Close()`.\nA deferred `Close` returns an error nothing can act on, and the error that\nmatters was already reported by `Err`. `errcheck` is configured to allow it.\n\nNever add an empty `case X:` to satisfy `exhaustive`. A switch that handles two\nof three values and answers the third after the switch says more than a case\nwith no body. `exhaustive` is disabled.\n\nA guard that can never fire is not a fix. `gosec` wanted a bound on a\nconversion of a PostgreSQL `server_version_num`, which is six digits, and the\nguard first written for it tested the wrong quantity and was never able to\nfire. That warning is excluded with its reason.\n\nRead `golangci-lint run` output as a list of questions, not a list of tasks.\n\n## Container images\n\n`container/` names every server release that `dbrun` starts, as Go data, one\nfile per product, and `container.All` in `container/container.go` joins them.\nMost are what dbmeta is tested against. The rest are Staged, because no model\nreads them: they are there for dbimp's drivers, for the flavors usql reaches,\nand for the emulators of hosted services, and CI does not run them (D118,\nD119). The package starts\nnothing and imports no container client, and it must not: a consumer brings\nits own podman, docker or Go client and picks its own version of it, the same\nway it brings its own driver.\n\nAn embedded database is not in there and must not be. SQLite3, DuckDB,\nmoderncsqlite, chai and csvq have no server and no container, and the\nrelease is whichever one the pinned Go driver ships. `dbrun` knows them\nitself (D116), and CI tests the ones a model reads in the same matrix as the\nservers, where `dbrun` starts nothing for them. chai and csvq have no\nmodel yet, so they are Staged (D119). See D42.\n\nThat list is the only copy. `test/cmd/dbrun` reads it, the CI workflow builds\nits matrix from `dbrun list --json --names`, and\n`container/workflow_test.go` fails when the workflow and the Go list disagree.\nChange the product's file in `container/` first and the workflow second.\n\nAdd a release there before you add it anywhere else.\n\n## Before you commit\n\nRun these:\n\n```bash\ngofmt -l . && go vet ./... && go build ./... && go test -race -count=2 ./...\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\n`gofmt -l .` must print nothing.\n\nRun it with `-count=2`, because that is what CI runs and a weaker local command\nhas already let two failures through. Both were the same fault: a test body\nthat registers a dialect or a query, which a registry rejects the second time.\nRegistration belongs in `init`.\n\nThat does not cover the `test` module. `./...` does not descend into a\ndirectory that has its own `go.mod`, so run the integration tests separately:\n\n```bash\ncd test && go test ./...\n```\n\nA test in the root module never opens a database connection. Test fragment\nmerging, version parsing and model selection there, against golden files. A\ntest that needs a server belongs in the `test` module.\n\nCI runs on GitHub Actions, on `ubuntu-latest` only. The queries are SQL and the\ncode is pure Go, so `dbmeta` does not need the runner matrix that the other\n`xo` projects use. Keep the workflow small.\n\nCI runs a release matrix, which D42 decided and which replaced the single\nlatest version D24 first called for. Every push runs the Tested tier, and\n`container.AtTier(container.Tested)` is the list. This file does not repeat\nit, because the copy written here went stale the moment a product was added,\nand it named six products for as long as ClickHouse, Trino, Presto, Firebird,\nSAP HANA and Apache Hive went on being added to the tier without it. Read the\nlist with:\n\n```bash\ncd test && go run ./cmd/dbrun list --json --names tested\n```\n\nThat command adds the embedded databases that a model reads, which are not\nin `container.AtTier` at all, and `dbrun` starts nothing for them. D42 keeps an\nembedded database out of the release list, and `dbrun` puts them back because\na person asking for the tier wants to run them too. The Nightly tier runs\nonce a night.\n\n`dbrun` builds the Cassandra image before it tests a Cassandra release, from\nthe Containerfile it embeds, because the published image refuses what the\nqueries read. See D62 and `test/cmd/dbrun/image/cassandra.Containerfile`.\n\nDo not write that list in the workflow from memory. It lives in `container/`,\n`container.AtTier` selects a tier, and\n`TestWorkflowReadsTheList` fails when the YAML and the Go disagree. Change the\nGo first and then the YAML. See D42 and D54.\n\nCI uses no database that is preinstalled on the runner. `dbrun` starts every\nrelease, and the compare job runs MariaDB 13.0 and MySQL 26.7 as service\ncontainers to check that the two products answer the same way (D44).\n\n## Writing documentation\n\nA new document goes in `docs/`. Only `README.md`, `AGENTS.md`, `CLAUDE.md` and\n`CONTRIBUTING.md` belong in the repository root, and a test enforces that. Add\nit to the table at the top of this file and to the one in `README.md`, because\na document nobody can find is a document nobody reads. See D50.\n\nA decision goes in a file of its own in `docs/decisions/` and nowhere else\n(D111). Name it `D<nnn>-<title>.md` with the next number. It opens with\n`# D<n>. <title>`, a blank line, and `Status: <status>.`, where the status is\n`Decided`, or `Amends D26`, or `Superseded by D24`. Add its row to\n`docs/decisions/README.md`. `TestTheDecisionIndexIsComplete` prints the row\nwhen it is missing. If your decision changes an earlier one, say so in both\nstatuses, because a reader who finds the older one has to be told.\n\nWrite plain English. Use short sentences and the active voice. Use `can`,\n`will`, and `must`, and do not use `should`, `may`, or `might`. Do not use\nsemicolons or em dashes. Put the condition before the command: \"If the query\nreturns no rows, return the empty set.\"\n\nLoad the `simple-english` skill before you write any text that a person reads:\na document, a code comment, an error message or a commit message. Follow it\nfor that text. Its rules include the ones above and add more, such as no\ncontractions and one word for one meaning. Ken asked for this. See D89 and\nD110.\n\n`TestProseIsSimpleEnglish` checks the rules of the skill that a machine can\ncheck: the banned modals, semicolons, dashes, contractions, bold, \"has been\",\nBritish spellings, filler words and Latin abbreviations. It reads every\ndocument, every comment in Go, YAML, a Containerfile, a batch file or an INI\nfile, and every error and test message. It skips text\nin backticks or double quotes. There is no marker that turns a rule off. If\nit reports a sentence, rewrite the sentence. Sentence length and the voice\nare still yours to check. See D156.\n\nWork that is known and not done goes in `docs/BACKLOG.md`, with the decision\nor the measurement that found it. When an item is done, delete it, and record\nanything decided in `docs/decisions/`.\n","isInternal":false,"tokens":8474,"sizeBytes":33333},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":11},{"name":"error-wrapping.md","path":".agents/skills/go-pedantry/examples/error-wrapping.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/go-pedantry/examples/error-wrapping.md","title":"Examples Skill","category":"anthropic-skill","format":"markdown","content":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically","isInternal":false,"tokens":2239,"sizeBytes":8512},{"name":"interface-design.md","path":".agents/skills/go-pedantry/examples/interface-design.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/go-pedantry/examples/interface-design.md","title":"Examples Skill","category":"anthropic-skill","format":"markdown","content":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable","isInternal":false,"tokens":2312,"sizeBytes":9685},{"name":"SKILL.md","path":".agents/skills/go-pedantry/SKILL.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/go-pedantry/SKILL.md","title":"go-pedantry","category":"anthropic-skill","format":"markdown","content":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint","frontmatter":{"name":"go-pedantry","description":"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.","version":"1.0.0"},"isInternal":false,"tokens":3542,"sizeBytes":14785},{"name":"rule-catalog.md","path":".agents/skills/simple-english/references/rule-catalog.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/simple-english/references/rule-catalog.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n","isInternal":false,"tokens":2676,"sizeBytes":10112},{"name":"strict-vocabulary.md","path":".agents/skills/simple-english/references/strict-vocabulary.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/simple-english/references/strict-vocabulary.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n","isInternal":false,"tokens":1254,"sizeBytes":5000},{"name":"use-cases.md","path":".agents/skills/simple-english/references/use-cases.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/simple-english/references/use-cases.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n","isInternal":false,"tokens":832,"sizeBytes":3870},{"name":"word-swaps.md","path":".agents/skills/simple-english/references/word-swaps.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/simple-english/references/word-swaps.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n","isInternal":false,"tokens":630,"sizeBytes":2493},{"name":"SKILL.md","path":".agents/skills/simple-english/SKILL.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.agents/skills/simple-english/SKILL.md","title":"simple-english","category":"anthropic-skill","format":"markdown","content":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n","frontmatter":{"name":"simple-english","description":"Write or rewrite text in plain, layman-readable English in the spirit of\nASD-STE100 Simplified Technical English: short sentences, active voice,\nsimple tenses, one word one meaning, condition before command, every\ntechnical term defined at first use, no AI slop. Default mode is Plain.\nStrict mode applies full STE vocabulary compliance when the user names\nSTE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\nprocedures, error messages, release notes, incident reports, API guides,\nand explanations for readers outside the field. Also use when the user\nsays \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n\"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\nreadable\", \"write for non-native readers\", or asks for docs that translate\nwell. The same rules govern the reply: answer first, prose only.\n","license":"MIT","compatibility":"claude-code cursor codex gemini-cli opencode","metadata":{"version":"2.1.0","standard":"ASD-STE100 Issue 9 (2025-01-15)"}},"isInternal":false,"tokens":1882,"sizeBytes":7891},{"name":"error-wrapping.md","path":".claude/skills/go-pedantry/examples/error-wrapping.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/go-pedantry/examples/error-wrapping.md","title":"Examples Skill","category":"anthropic-skill","format":"markdown","content":"# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically","isInternal":false,"tokens":2239,"sizeBytes":8512},{"name":"interface-design.md","path":".claude/skills/go-pedantry/examples/interface-design.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/go-pedantry/examples/interface-design.md","title":"Examples Skill","category":"anthropic-skill","format":"markdown","content":"# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable","isInternal":false,"tokens":2312,"sizeBytes":9685},{"name":"SKILL.md","path":".claude/skills/go-pedantry/SKILL.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/go-pedantry/SKILL.md","title":"go-pedantry","category":"anthropic-skill","format":"markdown","content":"---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint","frontmatter":{"name":"go-pedantry","description":"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.","version":"1.0.0"},"isInternal":false,"tokens":3542,"sizeBytes":14785},{"name":"rule-catalog.md","path":".claude/skills/simple-english/references/rule-catalog.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/simple-english/references/rule-catalog.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n","isInternal":false,"tokens":2676,"sizeBytes":10112},{"name":"strict-vocabulary.md","path":".claude/skills/simple-english/references/strict-vocabulary.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/simple-english/references/strict-vocabulary.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n","isInternal":false,"tokens":1254,"sizeBytes":5000},{"name":"use-cases.md","path":".claude/skills/simple-english/references/use-cases.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/simple-english/references/use-cases.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n","isInternal":false,"tokens":832,"sizeBytes":3870},{"name":"word-swaps.md","path":".claude/skills/simple-english/references/word-swaps.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/simple-english/references/word-swaps.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n","isInternal":false,"tokens":630,"sizeBytes":2493},{"name":"SKILL.md","path":".claude/skills/simple-english/SKILL.md","rawUrl":"https://raw.githubusercontent.com/xo/DBMETA/HEAD/.claude/skills/simple-english/SKILL.md","title":"simple-english","category":"anthropic-skill","format":"markdown","content":"---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n","frontmatter":{"name":"simple-english","description":"Write or rewrite text in plain, layman-readable English in the spirit of\nASD-STE100 Simplified Technical English: short sentences, active voice,\nsimple tenses, one word one meaning, condition before command, every\ntechnical term defined at first use, no AI slop. Default mode is Plain.\nStrict mode applies full STE vocabulary compliance when the user names\nSTE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\nprocedures, error messages, release notes, incident reports, API guides,\nand explanations for readers outside the field. Also use when the user\nsays \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n\"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\nreadable\", \"write for non-native readers\", or asks for docs that translate\nwell. The same rules govern the reply: answer first, prose only.\n","license":"MIT","compatibility":"claude-code cursor codex gemini-cli opencode","metadata":{"version":"2.1.0","standard":"ASD-STE100 Issue 9 (2025-01-15)"}},"isInternal":false,"tokens":1882,"sizeBytes":7891}],"systemPromptSnippet":"<yakaai_skills repo=\"xo/DBMETA\">\n<!-- File: AGENTS.md (Tokens: ~8474 | Category: root-instruction) -->\n# dbmeta\n\n`dbmeta` holds database metadata queries and models in one Go module. `usql`\nand `dbtpl` consume it.\n\n`dbmeta` only reads. It does not render metadata and it does not write it. The\n`Reader` and `Writer` pair from `usql` is a `usql` concept and it does not\ncome here.\n\nOne statement is the exception and it is a narrow one. `Dialect.ChangePassword`\nbuilds the statement that sets a password and returns it as text, because the\nstatement and its escaping are per product knowledge and a password cannot be\nbound as a parameter. It takes no database and runs nothing, so everything\n`dbmeta` executes is still a read. Do not widen that. See D56.\n\nEvery quoting rule lives here, and none in a consumer. A rule two products\nshare goes in the root package, as `QuoteLiteral` and `QuoteIdentifier` do,\nand a product's own goes beside its model. See D127.\n\n## Standing rules\n\nThese hold in every `xo` repository, for every coding agent (D110).\n\n1. Stage changes for review. Commit and push only when Ken says so.\n2. Load the `simple-english` skill before you write any text that a person\n   reads: a document, a code comment, an error message or a commit message.\n   Follow it for that text.\n3. Load the `go-pedantry` skill before you write or review Go code. Follow it\n   where it does not conflict with a rule in this file. A rule here wins.\n\n`CLAUDE.md` holds one line that imports this file, so that Claude Code and\nevery other agent read the same rules. Edit this file, not that one.\n\n## Which document to read\n\nTwo before anything else. `docs/NULLS.md` is the shortest and the one that cost\nthe most to learn. `docs/decisions/` holds every decision, one file each, and\n`docs/decisions/README.md` is a table of all 246. Read the status, because 103\nof them amend or replace an earlier one. Do not decide an open question on\nyour own. They are at the end of `docs/PLAN.md`. Ask Ken.\n\nThen by what you are doing:\n\n| If you are | Read |\n| --- | --- |\n| writing or changing any query | `docs/NULLS.md`, then `docs/COVERAGE.md` |\n| asking why something is the way it is | the index in `docs/decisions/README.md` |\n| adding an object kind | D46 and D47 in `docs/decisions/`, then `docs/COMMANDS.md` |\n| adding a database | `docs/DIALECT.md`, which is every step in order. It points at D66, `docs/EVALUATION.md`, D43, D154, D38 and D61 |\n| wondering which database comes next | D66, which holds the order and why a product with no container is last |\n| a parity failure | D61. Decide whether the query began depending on who is asking, or whether one release genuinely differs, and record it |\n| changing what a database answers | `docs/COVERAGE.md`, which is the record of what each one can do |\n| changing a fixture | D53, then run the fixture test in the root module. A core object belongs in every fixture |\n| a conformance failure | D53. Decide whether it is a fault or a product difference, then fix it or rewrite the expectation and say why |\n| deciding whether a field belongs here | D47 in `docs/decisions/`, which holds the cost test |\n| wiring up a client | `docs/COMMANDS.md`, then `docs/USQL.md` or `docs/DBTPL.md` |\n| designing the object set | `docs/QUERIES.md`, the survey of psql against information_schema |\n| adding a release to CI | the product's file in `container/`, which is the only copy of its list |\n| adding an old SQL Server that needs a Windows VM | `docs/DIALECT.md` for where the steps differ, then `docs/WINDOWS.md`, `container/windows.go` and D57 |\n| starting a database for any reason | `dbrun`, and nothing else. `docs/DBRUN.md` holds its use and the rules for a shared machine. Read those rules first |\n| adding a container or a machine that dbrun starts | `docs/CONTAINERS.md`, every step in order |\n| reaching a hosted service, such as Snowflake | `docs/DBRUN.md`, under Hosted services, and D117. Never enter a credential yourself |\n| running a product that needs a license file, such as Stardog | `docs/DBRUN.md`, under License files, and D118. Never sign up for one or download one yourself |\n| adding an entry for a product dbimp or usql reaches | D118, which holds the rules the entries follow and what each one cost to measure |\n| changing how a database is started | D68, D70, D75, D86, D97, D98, D105, D108 and D115 in `docs/decisions/`, then `docs/DBRUN.md` |\n| changing how dbrun calls podman or docker | D122. Read JSON, name many containers in one call, and do not ask twice |\n| provisioning a Windows machine | `docs/WINDOWS.md`, then `container/windows.go` and D57 |\n| testing against Cassandra | `test/cmd/dbrun/image/cassandra.Containerfile`. `dbrun` embeds it and builds it when the image is missing |\n| changing the CI workflow | D69. It reads the release list from `dbrun list --json` and names no image of its own |\n| answering a lint finding | the rule below, under Linting |\n| ignoring a build artifact | the root `.gitignore`, which is the only one. See D58 |\n| writing a document, a code comment, an error message or a commit message | the `simple-english` skill. Load it first. See below, under Writing documentation |\n| writing or reviewing Go code | the `go-pedantry` skill. Load it first. See Standing rules |\n| looking for work that is known and not done | `docs/BACKLOG.md` |\n| resuming a session that ended or crashed | `docs/PROGRESS.md`, which says where the work stands |\n| adding or updating an agent skill | `CONTRIBUTING.md`, under Agent skills, D89 and D110 |\n\n`CONTRIBUTING.md` is the same thing for a person, and shorter.\n\nA document that is not in that table does not exist. If you cannot find where\nsomething is written down, it is not written down, and it is an open question.\n\n## Hard rules\n\n1. The root module depends on the standard library and on nothing else. Its\n   `go.mod` has no `require` block and must keep it that way. Never add a\n   database driver, not even for a test. A consumer brings its own driver and\n   picks its own version of it, and `dbmeta` must not constrain that. Anything\n   needing a driver goes in the `test` module. Do not add any third party\n   package without asking Ken first.\n   D19 once made `github.com/xo/dburl` a direct dependency. It never became\n   one, because nothing here opens a connection: the caller passes a `DB` and\n   names a `Dialect`, so there is no URL to parse. That is the better answer\n   and it stands.\n   The rule that outlived the dependency is this one. Never write a scheme\n   list, an alias list, a flavor table, or a connection string parser in this\n   module. That taxonomy is `dburl`'s and repeating it is how two copies start\n   disagreeing. A consumer that has a URL reads `dburl` itself:\n   `URL.Dialect`, from dburl v0.32.0, is the dbmeta `Dialect` and selects the\n   model package, and `URL.OriginalScheme` holds an alias such as `mariadb`.\n   A product that speaks another product's protocol, such as `cockroachdb`\n   or `tidb`, has a `Dialect` of its own from dburl v0.36.0, so nothing else\n   names it. `URL.Driver` is the name `sql.Open` takes and never the dialect:\n   `pgx://` and `postgres://` both have the dialect `postgres`. See D99 and\n   D125.\n2. PostgreSQL is the primary model, and `psql` defines it. When two databases\n   describe the same object differently, follow `psql`. This decides the shape\n   of an answer. It does not require every database to answer every question.\n   `psql` sets the object model. It does not set the column set, and it never\n   did: `psql` prints what a person reads at a terminal, and a consumer needs\n   the parts. Return a fact `psql` does not print when one statement can\n   produce it, and never return prose as the only form of anything. D47 holds\n   the cost test, and rule 13 is the short version.\n3. A query that must differ between releases is one query with version\n   fragments, held as data in the one model package. There is no\n   package per release. A fragment must never change the set of columns a\n   query returns. Whichever side lacks a source pads, old or new: select the\n   column as `NULL AS name` on the server that has no source for it. A type\n   that differs between versions is cast to a common one, never left to `any`.\n   Where two products share a dialect, a fragment for one of them gates on\n   that product's version key and never on the number alone. MariaDB 11.8 and\n   MySQL 9 have no numeric relation, so a gate at `V(10, 2)` silently means\n   \"MariaDB only\" and shipped a wrong answer once already. Write\n   `Gate{Key: MariaDB, Min: V(10, 2)}`. Set a key only for a product you\n   detected. See D44.\n4. Stay backward compatible within reason. An old database keeps working when\n   support for a new one arrives. Every version sits in one of five tiers:\n   Tested in CI on every push, Nightly in CI once a night, Verified on a\n   development machine before a release and never in CI, Staged when no\n   model reads it yet, or Archived with no tests at all. Never call a version\n   supported without naming its tier, and never call a Staged one supported\n   at all. D40 set three, D42 added Nightly and D119 added Staged. The first\n   four are values of `container.Tier` and Archived is not, because an\n   archived release is one `container.All` does not name. A release is Staged\n   exactly when no model reads it. A Staged release records the cadence it\n   will have, Tested, Nightly or Verified, and takes it as its tier in the\n   change that adds its model. See D40, D42, D119 and D120.\n5. A query translated from a source tree that upstream no longer ships records\n   the release and commit of that tree beside the query. PostgreSQL 9.6 is the\n   case: `psql` dropped it in release 20, so there is nothing current to check\n   a translation against.\n6. Configuration is a value that the caller owns. Do not put a configured\n   value in a package level variable. One driver borrowed another driver's\n   configuration in `usql` and shipped a fault to users.\n7. Do not import `usql` or `dbtpl`. The dependency runs the other way.\n8. Every function that reads metadata takes `ctx context.Context` as its first\n   parameter. There are no exceptions. Call `QueryContext` and its relatives,\n   never `Query`, `Exec`, `QueryRow`, or `Prepare`. Never call\n   `context.Background()` or `context.TODO()` inside this library.\n9. Every model ships both its queries and its fixtures. The fixture creates one\n   of every object the queries read, so a test gets rows worth checking. It is\n   versioned like the queries, because syntax and objects arrive in different\n   releases, and a step the server is too old for is skipped rather than\n   refused. A query that has never run against a real server is not finished.\n10. The root module is pure Go and has no cgo, ever. It has no driver at all,\n   so there is nothing to argue about: a consumer builds it with\n   `CGO_ENABLED=0` and cross compiles it.\n   The `test` module can use cgo, because its own `go.mod` keeps it out of\n   everything a consumer builds. Where a database has a canonical cgo driver\n   and a pure Go one, test both: the canonical driver is what users run, and\n   the pure Go one is what a consumer who cannot use cgo runs, and the two\n   ship different library versions. Two drivers need cgo today, and only two:\n   `mattn/go-sqlite3`, which builds the real SQLite source and is the primary\n   SQLite driver, and `duckdb/duckdb-go`.\n   Never `godror`, which needs Oracle client libraries rather than only a C\n   compiler. See D48.\n   Compare the version query too. Adding or changing a dialect means reading\n   what `usql` runs for the same product, in the `Version` field of its\n   `drivers.Driver`, and recording the comparison in the statements table in\n   `docs/USQL.md`. A driver declaring no `Version` falls through to the generic\n   `SELECT version();`, and that counts as its statement. Read the field and\n   not the `drivers.Register` call: Oracle and godror both register through\n   `orshared.Register`, so a grep for the call says neither has a version\n   query and both do. The Oracle defect is real and it is a different one.\n   `usql` reads `v$instance`, which an ordinary user cannot see, so it prints\n   a version for an administrator and none for anybody else. Comparing\n   printed output never found that. `docs/USQL.md` holds the measurement.\n   `TestEveryModelIsInTheVersionTable` fails when a model has no row. See D38.\n   Every driver in the `test` module is the one the `dburl` registry names for\n   that dialect: `Scheme.GoPackage` of each scheme whose `Dialect` it is, from\n   dburl v0.29.0, with `Scheme.RequiresCGO` saying whether it needs a C\n   compiler. dburl is upstream of `dbmeta` and of `usql` both, and `usql` is\n   downstream, so its imports do not decide this. The version is the test\n   module's own, because the registry carries none. Where two schemes of one\n   dialect name two packages, test both as subtests named for the driver:\n   SQLite runs on `mattn/go-sqlite3` and `modernc.org/sqlite`, and PostgreSQL\n   on `jackc/pgx/v5/stdlib` and `lib/pq`. Parity is the one exception, and\n   D52 says why. The `go-ora/v3` release that dburl names panics rather than\n   connecting on 11g and 18c, and the fix is upstream and untagged. So the\n   Oracle tests pin v3 at the commit that fixes it, and move to the tag when\n   one holds the fix (D59, D157). If what `usql` imports differs from\n   what dburl names, the fault is in one of those two, and the fix goes\n   there. A query that works here and fails on the driver dburl names is a\n   query that does not work. See D52, D80 and D154.\n11. Never write a `//go:build` constraint on an operating system or an\n   architecture, and never branch on either. Testing is `linux/amd64` only.\n   The same database version is assumed to answer the same way everywhere.\n   A driver can carry its own platform builds, which is the driver's business.\n12. Write idiomatic Go. This code is a move of an older package, so a pattern\n   being present in the source is not a reason to keep it. See D18 in\n   `docs/decisions/` for the two patterns that must not carry over.\n13. `dbmeta` supplies the data and the consumer decides what to show. Never\n   withhold a fact, reorder one, or shape a result so that somebody's output\n   looks right. `usql` filters to match `psql` and `dbtpl` shows none of it,\n   and one set of queries serves both because no presentation is baked in.\n   A field can be added when one statement can produce it: a column already in\n   the row, a column reached by a join, or a correlated subquery whose plan\n   stays bounded as the catalog grows. It cannot when it needs a second\n   statement, a per row round trip, or a scan that grows with the whole\n   catalog. Check a new one with `EXPLAIN` against a catalog with thousands of\n   tables, not against a fixture with five.\n   A child of an object is its own kind with flat rows, never a slice on the\n   parent, because filling a slice needs a second statement or a dialect\n   specific aggregate. See D47.\n14. A new dialect is not finished until you ask several AI models\n   about the queries it cannot answer. `docs/DIALECT.md` holds every step of\n   adding one, and this is one of them. Consult at least two of Gemini,\n   DeepSeek and Astra, and ask each one to sort the unanswered queries into\n   three groups: absent from the product, present under another name, and\n   derivable from several catalog reads or one complex statement. A first pass\n   only finds the objects that the product names the way PostgreSQL names\n   them, and MariaDB proved the cost: 29 queries looked unanswerable until a\n   second opinion named the tables that hold four of them. Treat every answer\n   as a lead and run it against a real server. Leave an analogue that is a\n   stretch unsupported, and record it in `docs/COVERAGE.md` with the reason. See\n   D43.\n15. Never start a container by hand. `test/cmd/dbrun` starts every database\n   this project uses, and every container is named `<product>-<release>`, such\n   as `postgres-18` or `clickhouse-26.9`. Run\n   `cd test && go run ./cmd/dbrun help`, which lists every command, and\n   `docs/DBRUN.md` holds the same table, and the rules for sharing this\n   machine with other sessions. It takes `all` or a tier name\n   where it takes a server. The commands most often wanted are `start`,\n   `stop`, `remove`, `status`, `version`, `dsn`, `usql` and `test`. The rest\n   are in that table and this file does not repeat them.\n   This is not tidiness. A container started by hand gets a port somebody\n   typed rather than the one `container.All` assigns, so `dbrun version`\n   cannot reach a server that is plainly running, and two copies of the same\n   release end up on the machine with nothing to tell them apart. See D68.\n   If you are a coding agent, set `DBMETA_OWNER_NAME` to the name of your\n   session on every `dbrun` command, so that `status` shows whose each server\n   is. See D115.\n16. A dialect is not finished until you ask every query as the\n   administrator and as every lesser kind of principal the product has, and\n   the differences are written down. Add the principals to `parityTargets` in\n   `test/parity_test.go`, run `go test -run TestPrivilegeParity -update`, and\n   read the diff.\n   A principal is not one thing. SQL Server has three: a sysadmin, a server\n   login mapped to a database user, and a contained database user whose\n   password is in the database and which has no login at the server. Oracle\n   has the same three from 12c, where a common user is the login and a local\n   user in a pluggable database is the contained user. PostgreSQL and MySQL\n   have no containment, so each has a superuser or root, an owner, and a\n   grantee. SQLite and DuckDB have no user at all and the rule cannot reach\n   them.\n   Parity ships with the queries, the fixture and the documentation. They are\n   one deliverable. `TestEveryDialectIsMeasuredForParity` fails when a dialect\n   has neither a target nor an entry in `parityExempt` saying why it has none,\n   because `TestPrivilegeParity` says nothing about a target nobody wrote.\n   A scene the server is too old for carries a `min` and is skipped with the\n   reason, the way a fixture step is.\n   This is not a formality. It found six MariaDB queries that are refused\n   outright for a user holding ALL PRIVILEGES on its own database, because\n   they read tables in the `mysql` database rather than views that filter\n   themselves. Nothing had recorded that. See D61.\n\n## Layout\n\n- `/` is the root package `dbmeta`. It holds the driver agnostic API: the\n  object types, the `Query` values, the one method `Queryer` interface, the\n  `Args` filter, and the error values. External projects use this package.\n- `models/<driver>` holds one model, written by hand. One package covers every\n  supported version of that database. For example, `models/sqlite3`. A model\n  registers its queries from `init` and nothing generates it, so edit it\n  directly. See D71.\n- `all/` holds one file per model, such as `all/postgres.go`, each a blank\n  import gated by a build tag. Importing `all` registers the base set, and\n  `no_base`, a model's own name and `no_<model>` change which. A model can be\n  imported from `models/<driver>` instead. Say model, not driver: `dbmeta`\n  never opens a connection. See D31.\n- `container/` names every database release the tests run against, as Go data.\n  It starts no container and imports no container client. `test/cmd/dbrun` and\n  the CI workflow both read it, and a test fails when they drift.\n- `hosted/` names the hosted services, such as Snowflake and BigQuery, as Go\n  data. It holds no secret. `dbrun` resolves each one's connection string at\n  run time, and a service exists in `dbrun` only while it resolves. See D117.\n- `test/cmd/dbrun` is the only thing that starts a database, container or\n  virtual machine alike. `container/machine.go` holds the machine list, and\n  `container/windows.go` holds the Windows machines in it. `dbrun provision`\n  builds a Windows machine from the payload it embeds in\n  `test/cmd/dbrun/oem/`, and imports an appliance from the file a person\n  downloaded. Read `docs/WINDOWS.md`. See D57, D68 and D86.\n- `test/cmd/dbrun/image/` holds the Containerfiles this repository builds,\n  one for each product whose image dbrun builds. `dbrun` embeds them all and\n  builds `<product>.Containerfile` as `localhost/dbmeta/<product>`. See D118.\n  The Apache Cassandra image cannot be configured from the outside for what\n  the queries read, so the settings are baked in. `dbrun` embeds the file and\n  builds the image when it is missing.\n- `test/` is a separate module with its own `go.mod`. It holds the integration\n  tests and the database drivers. Neither can appear in the root module. It is\n  the only place cgo is allowed, and the separate `go.mod` is what makes that\n  safe.\n\nRead an existing model before you write one. `models/postgres` is the primary\none and `models/sqlite3` is a small one.\n\n## Writing a model\n\nNothing generates the code here. Every model is written, and no tool produces\nor reproduces it, so a file under `models/` is edited in place. See D71.\n\nA model is one package that registers a `Binding` per object kind from `init`.\nThe `Binding` carries the statement as `Stmt`, the `Fields` it returns, the\n`Params` it takes and a `Scan` function. A query that must differ between\nreleases is one statement with version fragments, which is rule 3.\n\nWrite the statement against a running server rather than from the\ndocumentation. `dbrun` is the only thing that starts one:\n\n```bash\ncd test && go run ./cmd/dbrun start postgres && go run ./cmd/dbrun usql postgres\n```\n\n`dbrun start` leaves the server up, which is what writing a query needs.\nRemove it with `dbrun remove postgres` when you are done. See D68.\n\nTwo rules catch the recurring NULL scan fault. Give a field `sql.Null[T]`\nwhenever the catalog column can be NULL, and select `NULL AS \"name\"` rather\nthan a literal where a release has no source for a column. A column that the\ncatalog declares NOT NULL can still arrive NULL through an outer join, so read\n`docs/NULLS.md` before deciding.\n\nA model is not finished until it has run against every release in its tier,\nits fixture builds one of every object its queries read, and its parity\ntargets exist. Those are one deliverable. See rule 9, rule 16 and D61.\n\n## Go conventions\n\nWrap every error with `%w`, never `%s` or `%v`:\n\n```go\nif err != nil {\n    return nil, fmt.Errorf(\"reading columns for %s: %w\", table, err)\n}\n```\n\nWrite error messages in lower case, starting with a gerund. Do not write\n\"failed to\" or \"error\". Name the object that failed.\n\nNever hide a NULL. Read `docs/NULLS.md` before writing a query for any database. It\nis the shortest document here and it is the one that has cost the most to\nlearn.\n\nIn brief: never wrap a nullable catalog column in `COALESCE`, and never pad an\nabsent column with a literal. Give the field the type `sql.Null[string]`, or\n`sql.Null[T]` for whatever T it is, and select `NULL AS \"name\"`. There is no\nalias for a nullable type and there must not be one: a reader of\n`go doc dbmeta.Sequence` learns from the field itself that it can be absent,\nand does not learn it from a name like `Text`. See D51. Before padding, ask whether the value on the old release is\nunknown or genuinely that value, and set `Field.Min` only for the first.\n\nDeclare sentinel errors as constants of a defined string type, never as\nvariables. A `var` declared with `errors.New` can be reassigned by any\nimporter. A constant cannot.\n\n```go\n// Error is an error.\ntype Error string\n\n// Error satisfies the error interface.\nfunc (err Error) Error() string {\n\treturn string(err)\n}\n\n// Error values.\nconst (\n\t// ErrNotSupported is the not supported error.\n\tErrNotSupported Error = \"not supported\"\n)\n```\n\nKeep the `Err` prefix and the PascalCase name. Compare with `errors.Is`.\n\nAccept interfaces and return concrete structs. Keep an interface to three\nmethods or fewer. Define an interface where it is consumed, not where it is\nimplemented.\n\nDo not name a type `Reader`. Everything in this module reads, so the word adds\nnothing and repeats the package.\n\nReturn an iterator, `iter.Seq2[Table, error]`. Read the result one record at a\ntime and never materialize it into a slice. Do not write a cursor type. An\nerror can arrive part way through a result, so a caller must be able to tell\nthe end of a result from a failure in the middle of one.\n\nAn iterator holds a database connection until it ends. Stopping early, by\n`break` or by `yield` returning false, must close the rows and release the\nconnection, and so must canceling the context. A caller that nests one\niterator inside another uses a second connection, which deadlocks on a pool of\none. Document the filter form that avoids nesting.\n\nUse generics rather than `interface{}` for a container of one type. Compare\nerrors with `errors.Is` and `errors.As`, never by string.\n\nPut `context.Context` first in every parameter list and name it `ctx`. Never\nstore it in a struct.\n\nName a package with one short lower case word. Do not stutter: write\n`postgres.Reader`, not `postgres.PostgresReader`.\n\nName a receiver with one or two lower case letters, and use the same name on\nevery method of the type. Never write `this` or `self`.\n\nGroup struct fields by purpose, with exported fields first and a blank line\nbetween groups.\n\n## Linting\n\n`.golangci.yml` holds the rules, and `test/.golangci.yml` holds them for the\ntest module. golangci-lint reads the `go.mod` of the directory it runs in, so\nboth are linted separately and CI runs it twice.\n\n```bash\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\nThe posture is `default: all` with a disable list. A new linter gets a look\nrather than silence, and that has paid: turning it on found a missing\n`rows.Err` check, a parameter shadowing the `min` builtin, and a copy loop that\n`maps.Copy` now does.\n\n### The rule for a lint finding\n\nA linter that makes idiomatic Go worse is disabled, with the reason written\nbeside it in the configuration. Only a real defect gets a code change. Ask\nwhich one it is before you touch the code, and if the remedy reads worse than\nwhat it replaces, that is the answer.\n\nA change made to quiet a linter rather than to fix something is itself a\ndefect. It leaves code that no reader can explain and that the next person\ncopies. Two were written here and reverted, and they are named so that they do\nnot come back:\n\nNever write `defer func() { _ = rows.Close() }()`. Write `defer rows.Close()`.\nA deferred `Close` returns an error nothing can act on, and the error that\nmatters was already reported by `Err`. `errcheck` is configured to allow it.\n\nNever add an empty `case X:` to satisfy `exhaustive`. A switch that handles two\nof three values and answers the third after the switch says more than a case\nwith no body. `exhaustive` is disabled.\n\nA guard that can never fire is not a fix. `gosec` wanted a bound on a\nconversion of a PostgreSQL `server_version_num`, which is six digits, and the\nguard first written for it tested the wrong quantity and was never able to\nfire. That warning is excluded with its reason.\n\nRead `golangci-lint run` output as a list of questions, not a list of tasks.\n\n## Container images\n\n`container/` names every server release that `dbrun` starts, as Go data, one\nfile per product, and `container.All` in `container/container.go` joins them.\nMost are what dbmeta is tested against. The rest are Staged, because no model\nreads them: they are there for dbimp's drivers, for the flavors usql reaches,\nand for the emulators of hosted services, and CI does not run them (D118,\nD119). The package starts\nnothing and imports no container client, and it must not: a consumer brings\nits own podman, docker or Go client and picks its own version of it, the same\nway it brings its own driver.\n\nAn embedded database is not in there and must not be. SQLite3, DuckDB,\nmoderncsqlite, chai and csvq have no server and no container, and the\nrelease is whichever one the pinned Go driver ships. `dbrun` knows them\nitself (D116), and CI tests the ones a model reads in the same matrix as the\nservers, where `dbrun` starts nothing for them. chai and csvq have no\nmodel yet, so they are Staged (D119). See D42.\n\nThat list is the only copy. `test/cmd/dbrun` reads it, the CI workflow builds\nits matrix from `dbrun list --json --names`, and\n`container/workflow_test.go` fails when the workflow and the Go list disagree.\nChange the product's file in `container/` first and the workflow second.\n\nAdd a release there before you add it anywhere else.\n\n## Before you commit\n\nRun these:\n\n```bash\ngofmt -l . && go vet ./... && go build ./... && go test -race -count=2 ./...\ngolangci-lint run ./... && (cd test && golangci-lint run ./...)\n```\n\n`gofmt -l .` must print nothing.\n\nRun it with `-count=2`, because that is what CI runs and a weaker local command\nhas already let two failures through. Both were the same fault: a test body\nthat registers a dialect or a query, which a registry rejects the second time.\nRegistration belongs in `init`.\n\nThat does not cover the `test` module. `./...` does not descend into a\ndirectory that has its own `go.mod`, so run the integration tests separately:\n\n```bash\ncd test && go test ./...\n```\n\nA test in the root module never opens a database connection. Test fragment\nmerging, version parsing and model selection there, against golden files. A\ntest that needs a server belongs in the `test` module.\n\nCI runs on GitHub Actions, on `ubuntu-latest` only. The queries are SQL and the\ncode is pure Go, so `dbmeta` does not need the runner matrix that the other\n`xo` projects use. Keep the workflow small.\n\nCI runs a release matrix, which D42 decided and which replaced the single\nlatest version D24 first called for. Every push runs the Tested tier, and\n`container.AtTier(container.Tested)` is the list. This file does not repeat\nit, because the copy written here went stale the moment a product was added,\nand it named six products for as long as ClickHouse, Trino, Presto, Firebird,\nSAP HANA and Apache Hive went on being added to the tier without it. Read the\nlist with:\n\n```bash\ncd test && go run ./cmd/dbrun list --json --names tested\n```\n\nThat command adds the embedded databases that a model reads, which are not\nin `container.AtTier` at all, and `dbrun` starts nothing for them. D42 keeps an\nembedded database out of the release list, and `dbrun` puts them back because\na person asking for the tier wants to run them too. The Nightly tier runs\nonce a night.\n\n`dbrun` builds the Cassandra image before it tests a Cassandra release, from\nthe Containerfile it embeds, because the published image refuses what the\nqueries read. See D62 and `test/cmd/dbrun/image/cassandra.Containerfile`.\n\nDo not write that list in the workflow from memory. It lives in `container/`,\n`container.AtTier` selects a tier, and\n`TestWorkflowReadsTheList` fails when the YAML and the Go disagree. Change the\nGo first and then the YAML. See D42 and D54.\n\nCI uses no database that is preinstalled on the runner. `dbrun` starts every\nrelease, and the compare job runs MariaDB 13.0 and MySQL 26.7 as service\ncontainers to check that the two products answer the same way (D44).\n\n## Writing documentation\n\nA new document goes in `docs/`. Only `README.md`, `AGENTS.md`, `CLAUDE.md` and\n`CONTRIBUTING.md` belong in the repository root, and a test enforces that. Add\nit to the table at the top of this file and to the one in `README.md`, because\na document nobody can find is a document nobody reads. See D50.\n\nA decision goes in a file of its own in `docs/decisions/` and nowhere else\n(D111). Name it `D<nnn>-<title>.md` with the next number. It opens with\n`# D<n>. <title>`, a blank line, and `Status: <status>.`, where the status is\n`Decided`, or `Amends D26`, or `Superseded by D24`. Add its row to\n`docs/decisions/README.md`. `TestTheDecisionIndexIsComplete` prints the row\nwhen it is missing. If your decision changes an earlier one, say so in both\nstatuses, because a reader who finds the older one has to be told.\n\nWrite plain English. Use short sentences and the active voice. Use `can`,\n`will`, and `must`, and do not use `should`, `may`, or `might`. Do not use\nsemicolons or em dashes. Put the condition before the command: \"If the query\nreturns no rows, return the empty set.\"\n\nLoad the `simple-english` skill before you write any text that a person reads:\na document, a code comment, an error message or a commit message. Follow it\nfor that text. Its rules include the ones above and add more, such as no\ncontractions and one word for one meaning. Ken asked for this. See D89 and\nD110.\n\n`TestProseIsSimpleEnglish` checks the rules of the skill that a machine can\ncheck: the banned modals, semicolons, dashes, contractions, bold, \"has been\",\nBritish spellings, filler words and Latin abbreviations. It reads every\ndocument, every comment in Go, YAML, a Containerfile, a batch file or an INI\nfile, and every error and test message. It skips text\nin backticks or double quotes. There is no marker that turns a rule off. If\nit reports a sentence, rewrite the sentence. Sentence length and the voice\nare still yours to check. See D156.\n\nWork that is known and not done goes in `docs/BACKLOG.md`, with the decision\nor the measurement that found it. When an item is done, delete it, and record\nanything decided in `docs/decisions/`.\n\n<!-- File: CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\n@AGENTS.md\n\n<!-- File: .agents/skills/go-pedantry/examples/error-wrapping.md (Tokens: ~2239 | Category: anthropic-skill) -->\n# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically\n\n<!-- File: .agents/skills/go-pedantry/examples/interface-design.md (Tokens: ~2312 | Category: anthropic-skill) -->\n# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable\n\n<!-- File: .agents/skills/go-pedantry/SKILL.md (Tokens: ~3542 | Category: anthropic-skill) -->\n---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint\n\n<!-- File: .agents/skills/simple-english/references/rule-catalog.md (Tokens: ~2676 | Category: anthropic-skill) -->\n# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n<!-- File: .agents/skills/simple-english/references/strict-vocabulary.md (Tokens: ~1254 | Category: anthropic-skill) -->\n# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n\n<!-- File: .agents/skills/simple-english/references/use-cases.md (Tokens: ~832 | Category: anthropic-skill) -->\n# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n\n<!-- File: .agents/skills/simple-english/references/word-swaps.md (Tokens: ~630 | Category: anthropic-skill) -->\n# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n\n<!-- File: .agents/skills/simple-english/SKILL.md (Tokens: ~1882 | Category: anthropic-skill) -->\n---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n\n<!-- File: .claude/skills/go-pedantry/examples/error-wrapping.md (Tokens: ~2239 | Category: anthropic-skill) -->\n# Error Wrapping Patterns in Go\n\nComplete examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, custom error types, and how `errors.Is` / `errors.As` work with a properly wrapped error chain.\n\n## Basic Error Wrapping\n\n### Bad -- Error Chain Destroyed\n\n```go\npackage order\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %s destroys the error chain -- errors.Is(err, sql.ErrNoRows) will fail\n\t\treturn nil, fmt.Errorf(\"failed to get order %s: %s\", id, err.Error())\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\t// %v also destroys the chain\n\t\treturn nil, fmt.Errorf(\"error fetching items: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\t// ...\n\treturn items, nil\n}\n```\n\n### Good -- Error Chain Preserved\n\n```go\npackage order\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\trow := db.QueryRowContext(ctx, \"SELECT * FROM orders WHERE id = $1\", id)\n\n\tvar order Order\n\terr := row.Scan(&order.ID, &order.Total, &order.Status)\n\tif err != nil {\n\t\t// %w preserves the chain -- errors.Is(err, sql.ErrNoRows) works\n\t\treturn nil, fmt.Errorf(\"getting order %s: %w\", id, err)\n\t}\n\n\treturn &order, nil\n}\n\nfunc GetOrderItems(ctx context.Context, orderID string) ([]Item, error) {\n\trows, err := db.QueryContext(ctx, \"SELECT * FROM items WHERE order_id = $1\", orderID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying items for order %s: %w\", orderID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar items []Item\n\tfor rows.Next() {\n\t\tvar item Item\n\t\tif err := rows.Scan(&item.ID, &item.Name, &item.Price); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scanning item row for order %s: %w\", orderID, err)\n\t\t}\n\t\titems = append(items, item)\n\t}\n\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"iterating items for order %s: %w\", orderID, err)\n\t}\n\n\treturn items, nil\n}\n```\n\n## Error Message Conventions\n\n```go\n// BAD -- verbose, redundant, inconsistent\nreturn nil, fmt.Errorf(\"Failed to connect to database: %w\", err)         // capitalized\nreturn nil, fmt.Errorf(\"error: could not parse config file: %w\", err)     // \"error:\" is redundant\nreturn nil, fmt.Errorf(\"an error occurred while fetching user: %w\", err)  // too verbose\nreturn nil, fmt.Errorf(\"Error in ProcessOrder: %w\", err)                  // function name in error\n\n// GOOD -- terse, lowercase, context-rich\nreturn nil, fmt.Errorf(\"connecting to database at %s: %w\", addr, err)\nreturn nil, fmt.Errorf(\"parsing config %s: %w\", path, err)\nreturn nil, fmt.Errorf(\"fetching user %s: %w\", userID, err)\nreturn nil, fmt.Errorf(\"processing order %s: %w\", orderID, err)\n```\n\n**Rules:**\n- Start with a lowercase gerund: \"getting\", \"parsing\", \"connecting\", \"validating\"\n- No \"failed to\" -- the error context already implies failure\n- No \"error:\" prefix -- it is already an error\n- No function names -- the stack trace has that\n- Include identifying information: IDs, file paths, URLs, counts\n\n## Sentinel Errors\n\n```go\npackage order\n\nimport \"errors\"\n\n// Sentinel errors -- exported, Err prefix, PascalCase\nvar (\n\tErrNotFound      = errors.New(\"order not found\")\n\tErrAlreadyPaid   = errors.New(\"order already paid\")\n\tErrInvalidStatus = errors.New(\"invalid order status transition\")\n\tErrExpired       = errors.New(\"order expired\")\n)\n\nfunc GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := db.QueryOrder(ctx, id)\n\tif errors.Is(err, sql.ErrNoRows) {\n\t\treturn nil, ErrNotFound  // wrap with domain error\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t}\n\treturn order, nil\n}\n\nfunc PayOrder(ctx context.Context, id string) error {\n\torder, err := GetOrder(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"paying order %s: %w\", id, err)\n\t}\n\n\tif order.Status == StatusPaid {\n\t\treturn ErrAlreadyPaid\n\t}\n\n\tif order.ExpiresAt.Before(time.Now()) {\n\t\treturn ErrExpired\n\t}\n\n\t// process payment...\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.Is to check sentinel errors through the chain\nerr := PayOrder(ctx, \"ord_123\")\nif errors.Is(err, order.ErrNotFound) {\n\thttp.Error(w, \"Order not found\", http.StatusNotFound)\n\treturn\n}\nif errors.Is(err, order.ErrAlreadyPaid) {\n\thttp.Error(w, \"Order already paid\", http.StatusConflict)\n\treturn\n}\nif errors.Is(err, order.ErrExpired) {\n\thttp.Error(w, \"Order expired\", http.StatusGone)\n\treturn\n}\nif err != nil {\n\tslog.Error(\"failed to pay order\", \"error\", err)\n\thttp.Error(w, \"Internal error\", http.StatusInternalServerError)\n\treturn\n}\n```\n\n## Custom Error Types\n\nWhen an error needs to carry structured data (not just a message), define a custom error type.\n\n```go\npackage validation\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrValidation carries field-level error details.\ntype ErrValidation struct {\n\tField   string\n\tMessage string\n\tValue   any\n}\n\nfunc (e *ErrValidation) Error() string {\n\treturn fmt.Sprintf(\"validation error on %s: %s (got %v)\", e.Field, e.Message, e.Value)\n}\n\n// ErrMultiValidation aggregates multiple validation errors.\ntype ErrMultiValidation struct {\n\tErrors []*ErrValidation\n}\n\nfunc (e *ErrMultiValidation) Error() string {\n\tmsgs := make([]string, len(e.Errors))\n\tfor i, err := range e.Errors {\n\t\tmsgs[i] = err.Error()\n\t}\n\treturn fmt.Sprintf(\"%d validation errors: %s\", len(e.Errors), strings.Join(msgs, \"; \"))\n}\n\n// Usage\nfunc ValidateOrder(order *Order) error {\n\tvar errs []*ErrValidation\n\n\tif order.Total <= 0 {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"total\",\n\t\t\tMessage: \"must be positive\",\n\t\t\tValue:   order.Total,\n\t\t})\n\t}\n\n\tif order.CustomerID == \"\" {\n\t\terrs = append(errs, &ErrValidation{\n\t\t\tField:   \"customer_id\",\n\t\t\tMessage: \"is required\",\n\t\t\tValue:   order.CustomerID,\n\t\t})\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn &ErrMultiValidation{Errors: errs}\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Caller uses errors.As to extract the custom error type\nerr := ValidateOrder(order)\nif err != nil {\n\tvar multiErr *validation.ErrMultiValidation\n\tif errors.As(err, &multiErr) {\n\t\tfor _, ve := range multiErr.Errors {\n\t\t\tfmt.Printf(\"Field %s: %s\\n\", ve.Field, ve.Message)\n\t\t}\n\t\treturn\n\t}\n\t// not a validation error -- something else went wrong\n\tslog.Error(\"unexpected error validating order\", \"error\", err)\n}\n```\n\n## Error Wrapping Through Layers\n\nA properly wrapped error chain reads like a stack trace in reverse, showing the path from the highest-level operation down to the root cause.\n\n```go\n// Layer 1: database\nfunc (r *PostgresOrderRepo) GetByID(ctx context.Context, id string) (*Order, error) {\n\t// ...\n\treturn nil, fmt.Errorf(\"querying order %s: %w\", id, err)\n\t// Error: \"querying order ord_123: connection refused\"\n}\n\n// Layer 2: service\nfunc (s *OrderService) GetOrder(ctx context.Context, id string) (*Order, error) {\n\torder, err := s.repo.GetByID(ctx, id)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting order from repo: %w\", err)\n\t}\n\t// Error: \"getting order from repo: querying order ord_123: connection refused\"\n\treturn order, nil\n}\n\n// Layer 3: handler\nfunc (h *OrderHandler) HandleGetOrder(w http.ResponseWriter, r *http.Request) {\n\torder, err := h.service.GetOrder(r.Context(), id)\n\tif err != nil {\n\t\tslog.Error(\"handling get order request\", \"order_id\", id, \"error\", err)\n\t\t// Log: \"handling get order request order_id=ord_123\n\t\t//        error=getting order from repo: querying order ord_123: connection refused\"\n\t}\n}\n```\n\nEach layer adds context about what it was trying to do. The full chain tells the complete story: the handler was handling a get request, the service was getting from the repo, the repo was querying, and the root cause was a connection refused.\n\n## Key Points\n\n- `%w` preserves the error chain; `%s` and `%v` destroy it -- this is the single most common Go error-handling mistake\n- Error messages are lowercase, start with a gerund, include IDs, and do not include \"failed to\" or function names\n- Sentinel errors (`ErrNotFound`) are checked with `errors.Is()`; custom error types (`*ErrValidation`) are extracted with `errors.As()`\n- Every layer in the call stack wraps with additional context -- the full chain reads as a narrative from high level to root cause\n- `errorlint` in golangci-lint catches `%s`/`%v` on errors and `==` instead of `errors.Is()` automatically\n\n<!-- File: .claude/skills/go-pedantry/examples/interface-design.md (Tokens: ~2312 | Category: anthropic-skill) -->\n# Interface Design Patterns in Go\n\nGo examples showing the core interface design principles: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention. Every example demonstrates why Go interfaces work differently from Java/C# interfaces and how to use them idiomatically.\n\n## Accept Interfaces, Return Structs\n\n### Bad -- Returning Interfaces\n\n```go\npackage user\n\n// UserStore is defined in the same package as the implementation -- wrong location\ntype UserStore interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n\tDelete(ctx context.Context, id string) error\n\tList(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n\n// Returns an interface -- hides the concrete type, prevents access to implementation-specific methods\nfunc NewPostgresStore(db *sql.DB) UserStore {\n\treturn &postgresStore{db: db}\n}\n\ntype postgresStore struct {\n\tdb *sql.DB\n}\n\nfunc (s *postgresStore) Get(ctx context.Context, id string) (*User, error) { ... }\nfunc (s *postgresStore) Save(ctx context.Context, user *User) error { ... }\nfunc (s *postgresStore) Delete(ctx context.Context, id string) error { ... }\nfunc (s *postgresStore) List(ctx context.Context, opts ListOptions) ([]*User, error) { ... }\n```\n\n### Good -- Returning Concrete Struct\n\n```go\npackage postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n)\n\n// UserStore is a concrete struct -- no interface in this package\ntype UserStore struct {\n\tdb *sql.DB\n}\n\n// Returns *UserStore, not an interface\nfunc NewUserStore(db *sql.DB) *UserStore {\n\treturn &UserStore{db: db}\n}\n\nfunc (s *UserStore) Get(ctx context.Context, id string) (*User, error) {\n\trow := s.db.QueryRowContext(ctx, \"SELECT id, name, email FROM users WHERE id = $1\", id)\n\n\tvar user User\n\tif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n\t}\n\n\treturn &user, nil\n}\n\nfunc (s *UserStore) Save(ctx context.Context, user *User) error {\n\t_, err := s.db.ExecContext(ctx,\n\t\t\"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3\",\n\t\tuser.ID, user.Name, user.Email,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"saving user %s: %w\", user.ID, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) Delete(ctx context.Context, id string) error {\n\t_, err := s.db.ExecContext(ctx, \"DELETE FROM users WHERE id = $1\", id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"deleting user %s: %w\", id, err)\n\t}\n\treturn nil\n}\n\nfunc (s *UserStore) List(ctx context.Context, opts ListOptions) ([]*User, error) {\n\t// ...\n\treturn users, nil\n}\n```\n\n## Consumer-Side Interface Definition\n\nThe consumer defines the interface it needs. The implementation satisfies it implicitly.\n\n```go\n// package handler -- the CONSUMER defines the interface\n\npackage handler\n\nimport \"context\"\n\n// UserGetter is what the handler needs -- just one method\ntype UserGetter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n\tusers UserGetter  // accepts interface\n}\n\nfunc NewGetUserHandler(users UserGetter) *GetUserHandler {\n\treturn &GetUserHandler{users: users}\n}\n\nfunc (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\n\tuser, err := h.users.Get(r.Context(), id)\n\tif err != nil {\n\t\twriteError(w, err)\n\t\treturn\n\t}\n\n\twriteJSON(w, http.StatusOK, user)\n}\n```\n\n```go\n// package service -- another CONSUMER, different interface\n\npackage service\n\nimport \"context\"\n\n// UserReadWriter is what the service needs -- two methods\ntype UserReadWriter interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype UserService struct {\n\tusers UserReadWriter  // accepts interface\n}\n\nfunc NewUserService(users UserReadWriter) *UserService {\n\treturn &UserService{users: users}\n}\n\nfunc (s *UserService) UpdateEmail(ctx context.Context, id string, newEmail string) error {\n\tuser, err := s.users.Get(ctx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"updating email for user %s: %w\", id, err)\n\t}\n\n\tuser.Email = newEmail\n\n\tif err := s.users.Save(ctx, user); err != nil {\n\t\treturn fmt.Errorf(\"saving user %s after email update: %w\", id, err)\n\t}\n\n\treturn nil\n}\n```\n\n```go\n// Wiring -- the concrete type satisfies both interfaces without knowing about them\n\npackage main\n\nfunc main() {\n\tdb := connectDB()\n\tstore := postgres.NewUserStore(db)  // returns *postgres.UserStore\n\n\t// *postgres.UserStore satisfies handler.UserGetter (has Get method)\n\tgetUserHandler := handler.NewGetUserHandler(store)\n\n\t// *postgres.UserStore satisfies service.UserReadWriter (has Get and Save methods)\n\tuserService := service.NewUserService(store)\n\n\t// ...\n}\n```\n\n**Why this works:** `postgres.UserStore` has `Get()` and `Save()` methods. The handler only needs `Get()`, so it defines a one-method interface. The service needs `Get()` and `Save()`, so it defines a two-method interface. Neither interface is declared in the `postgres` package. Neither consumer imports the other. The concrete type satisfies both implicitly.\n\n## Small Interfaces and Composition\n\n### Bad -- One Giant Interface\n\n```go\ntype Storage interface {\n\tGetUser(ctx context.Context, id string) (*User, error)\n\tSaveUser(ctx context.Context, user *User) error\n\tDeleteUser(ctx context.Context, id string) error\n\tGetOrder(ctx context.Context, id string) (*Order, error)\n\tSaveOrder(ctx context.Context, order *Order) error\n\tGetProduct(ctx context.Context, id string) (*Product, error)\n\tSaveProduct(ctx context.Context, product *Product) error\n\tRunMigrations(ctx context.Context) error\n\tPing(ctx context.Context) error\n\tClose() error\n}\n```\n\n### Good -- Composed Small Interfaces\n\n```go\n// Small, focused interfaces\ntype Reader interface {\n\tRead(ctx context.Context, id string) (*Entity, error)\n}\n\ntype Writer interface {\n\tWrite(ctx context.Context, entity *Entity) error\n}\n\ntype Deleter interface {\n\tDelete(ctx context.Context, id string) error\n}\n\n// Compose when a consumer needs multiple capabilities\ntype ReadWriter interface {\n\tReader\n\tWriter\n}\n\ntype ReadWriteDeleter interface {\n\tReader\n\tWriter\n\tDeleter\n}\n\n// Health check -- separate concern\ntype HealthChecker interface {\n\tPing(ctx context.Context) error\n}\n```\n\n## Testing with Interfaces\n\nConsumer-side interfaces make testing trivial. The test defines a mock that satisfies just the interface it needs.\n\n```go\npackage handler_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\n// Mock implements just UserGetter -- the interface the handler needs\ntype mockUserGetter struct {\n\tuser *User\n\terr  error\n}\n\nfunc (m *mockUserGetter) Get(ctx context.Context, id string) (*User, error) {\n\treturn m.user, m.err\n}\n\nfunc TestGetUserHandler_Success(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\tuser: &User{ID: \"usr_123\", Name: \"Alice\", Email: \"alice@example.com\"},\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_123\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200, got %d\", rec.Code)\n\t}\n}\n\nfunc TestGetUserHandler_NotFound(t *testing.T) {\n\tmock := &mockUserGetter{\n\t\terr: ErrNotFound,\n\t}\n\n\thandler := NewGetUserHandler(mock)\n\n\treq := httptest.NewRequest(\"GET\", \"/users/usr_999\", nil)\n\trec := httptest.NewRecorder()\n\n\thandler.ServeHTTP(rec, req)\n\n\tif rec.Code != http.StatusNotFound {\n\t\tt.Errorf(\"expected 404, got %d\", rec.Code)\n\t}\n}\n```\n\nNo mocking framework needed. No code generation. The mock is 8 lines. This is only possible because the interface is small (one method) and defined at the consumer side.\n\n## Verb+er Naming for Single-Method Interfaces\n\n```go\n// Standard library examples -- the pattern to follow\ntype Reader interface {\n\tRead(p []byte) (n int, err error)\n}\n\ntype Writer interface {\n\tWrite(p []byte) (n int, err error)\n}\n\ntype Closer interface {\n\tClose() error\n}\n\ntype Stringer interface {\n\tString() string\n}\n\n// Application examples -- same pattern\ntype Validator interface {\n\tValidate() error\n}\n\ntype Formatter interface {\n\tFormat(data any) (string, error)\n}\n\ntype Notifier interface {\n\tNotify(ctx context.Context, event Event) error\n}\n\ntype Processor interface {\n\tProcess(ctx context.Context, job Job) error\n}\n\ntype Authenticator interface {\n\tAuthenticate(ctx context.Context, token string) (*User, error)\n}\n\ntype Serializer interface {\n\tSerialize(v any) ([]byte, error)\n}\n```\n\n**When the Verb+er pattern does not apply** (multi-method interfaces), use a descriptive noun:\n\n```go\n// Multi-method interfaces use descriptive names\ntype UserRepository interface {\n\tGet(ctx context.Context, id string) (*User, error)\n\tSave(ctx context.Context, user *User) error\n}\n\ntype EventPublisher interface {\n\tPublish(ctx context.Context, event Event) error\n\tSubscribe(ctx context.Context, topic string, handler EventHandler) error\n}\n```\n\n## Key Points\n\n- Go interfaces are satisfied implicitly -- the implementation never imports or references the interface\n- Define interfaces at the consumer side, where they are used, not at the implementation side\n- Return concrete structs from constructors -- the caller decides which interface the struct satisfies based on how it uses it\n- Keep interfaces to 1-3 methods -- the smaller the interface, the more implementations can satisfy it\n- Single-method interfaces use Verb+er naming (`Reader`, `Writer`, `Validator`)\n- Compose small interfaces (`ReadWriter = Reader + Writer`) instead of defining large ones\n- Small interfaces make testing trivial -- mocks are a few lines, no framework needed\n- The standard library is the best reference: `io.Reader`, `io.Writer`, `fmt.Stringer`, `sort.Interface` -- all small, all consumer-side, all composable\n\n<!-- File: .claude/skills/go-pedantry/SKILL.md (Tokens: ~3542 | Category: anthropic-skill) -->\n---\nname: go-pedantry\ndescription: \"This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs), package naming conventions, struct field ordering, receiver naming, golangci-lint configuration, and Go-specific patterns that go beyond universal principles.\"\nversion: 1.0.0\n---\n\n# Go Is Opinionated -- So Are We\n\nGo is a language that already has strong opinions: `gofmt`, short variable names, explicit error handling, no exceptions. But the language's opinions stop at syntax. How you wrap errors, design interfaces, name packages, order struct fields, and configure your linter -- these are decisions Go leaves to you. This skill makes those decisions for you, because inconsistency in these areas is what turns a Go codebase from clean to chaotic.\n\n## Error Wrapping: Always `%w`, Never `%s` or `%v`\n\nWhen wrapping an error with `fmt.Errorf`, ALWAYS use `%w`. This preserves the error chain so that `errors.Is()` and `errors.As()` work. Using `%s` or `%v` converts the error to a string and destroys the chain. This is not a style preference -- it is the difference between errors that can be programmatically handled and errors that can only be logged.\n\n```go\n// BAD -- error chain is destroyed, errors.Is() will not work\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %s\", id, err.Error())\n    }\n    return user, nil\n}\n\n// BAD -- %v also destroys the chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"failed to get user %s: %v\", id, err)\n    }\n    return user, nil\n}\n```\n\n```go\n// GOOD -- %w preserves the error chain\nfunc GetUser(ctx context.Context, id string) (*User, error) {\n    user, err := db.QueryUser(ctx, id)\n    if err != nil {\n        return nil, fmt.Errorf(\"getting user %s: %w\", id, err)\n    }\n    return user, nil\n}\n\n// Now callers can inspect the error chain:\nuser, err := GetUser(ctx, \"usr_123\")\nif errors.Is(err, sql.ErrNoRows) {\n    // handle not found\n}\n```\n\n**Error message conventions:**\n- Start with a lowercase verb in gerund form: \"getting user\", \"parsing config\", \"connecting to database\"\n- Do NOT start with \"failed to\" or \"error\" -- the fact that it is an error is already clear from the context\n- Add identifying information: IDs, names, paths -- whatever helps debug the issue\n- Keep it terse: `\"getting user %s: %w\"` not `\"an error occurred while attempting to retrieve the user with ID %s from the database: %w\"`\n\n## Error Variable Naming\n\nSentinel errors (package-level error values) follow a strict naming convention:\n\n```go\n// GOOD -- exported, Err prefix, PascalCase after prefix\nvar (\n    ErrNotFound      = errors.New(\"not found\")\n    ErrAlreadyExists = errors.New(\"already exists\")\n    ErrInvalidInput  = errors.New(\"invalid input\")\n    ErrUnauthorized  = errors.New(\"unauthorized\")\n    ErrRateLimited   = errors.New(\"rate limited\")\n)\n```\n\n```go\n// BAD -- every one of these violates the convention\nvar (\n    NotFoundError = errors.New(\"not found\")     // wrong: no Err prefix\n    errnotfound   = errors.New(\"not found\")     // wrong: unexported + no camelCase\n    NOT_FOUND     = errors.New(\"not found\")     // wrong: not Go convention\n    UserNotFound  = errors.New(\"user not found\") // wrong: no Err prefix\n)\n```\n\nCustom error types follow the same prefix pattern:\n\n```go\n// GOOD\ntype ErrValidation struct {\n    Field   string\n    Message string\n}\n\nfunc (e *ErrValidation) Error() string {\n    return fmt.Sprintf(\"validation error on %s: %s\", e.Field, e.Message)\n}\n```\n\n## Interface Design\n\n### Accept Interfaces, Return Structs\n\nFunctions accept interfaces so they can work with any implementation. Functions return concrete structs so the caller has the full type. This is the fundamental Go interface rule.\n\n```go\n// BAD -- returning an interface hides the concrete type\nfunc NewUserService(db Database) UserServiceInterface {\n    return &UserService{db: db}\n}\n\n// BAD -- accepting a concrete type prevents testing and alternative implementations\nfunc ProcessOrder(service *StripeService, order *Order) error {\n    return service.Charge(order.Total)\n}\n```\n\n```go\n// GOOD -- accept interface, return struct\nfunc NewUserService(db UserRepository) *UserService {\n    return &UserService{db: db}\n}\n\n// GOOD -- accept interface for the dependency\nfunc ProcessOrder(payment PaymentProcessor, order *Order) error {\n    return payment.Charge(order.Total)\n}\n```\n\n### Keep Interfaces Small\n\nGo interfaces should be small. One to three methods. If your interface has more than three methods, it is probably doing too much.\n\n```go\n// BAD -- too many methods, too specific\ntype UserManager interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n    SearchUsers(ctx context.Context, query string) ([]*User, error)\n    CountUsers(ctx context.Context) (int, error)\n}\n```\n\n```go\n// GOOD -- small, focused interfaces\ntype UserReader interface {\n    GetUser(ctx context.Context, id string) (*User, error)\n}\n\ntype UserWriter interface {\n    CreateUser(ctx context.Context, input CreateUserInput) (*User, error)\n    UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*User, error)\n    DeleteUser(ctx context.Context, id string) error\n}\n\ntype UserLister interface {\n    ListUsers(ctx context.Context, opts ListOptions) ([]*User, error)\n}\n```\n\n### Define Interfaces at the Consumer Side\n\nInterfaces belong where they are used, not where they are implemented. The consumer defines what it needs; the implementation satisfies it without knowing.\n\n```go\n// BAD -- interface defined in the implementation package\n// package database\ntype UserStore interface {\n    Get(ctx context.Context, id string) (*User, error)\n    Save(ctx context.Context, user *User) error\n}\n\ntype PostgresUserStore struct { ... }\n```\n\n```go\n// GOOD -- interface defined where it is used\n// package handler (the consumer)\ntype UserGetter interface {\n    Get(ctx context.Context, id string) (*User, error)\n}\n\ntype GetUserHandler struct {\n    users UserGetter  // accepts interface\n}\n\n// package database (the implementation)\ntype PostgresUserStore struct { ... }\n\nfunc (s *PostgresUserStore) Get(ctx context.Context, id string) (*User, error) { ... }\n// PostgresUserStore satisfies handler.UserGetter without importing it\n```\n\n### Single-Method Interface Naming\n\nSingle-method interfaces are named: Verb + \"er\".\n\n| Method | Interface Name |\n|--------|---------------|\n| `Read()` | `Reader` |\n| `Write()` | `Writer` |\n| `Close()` | `Closer` |\n| `Format()` | `Formatter` |\n| `Validate()` | `Validator` |\n| `Handle()` | `Handler` |\n| `Encode()` | `Encoder` |\n\n## Package Naming\n\nGo packages have strict naming conventions that the community enforces by reputation.\n\n**Rules:**\n1. Short, lowercase, single word: `user`, `auth`, `config`, `order`\n2. No underscores, no camelCase: `userservice` not `userService` or `user_service`\n3. No stuttering: the package name is part of the qualified name. `user.Service` not `user.UserService`\n4. No generic names: `utils`, `helpers`, `common`, `misc`, `shared`, `base` -- these are organizational failures\n\n```go\n// BAD -- package naming violations\npackage userService        // camelCase\npackage user_service       // underscores\npackage utils              // meaningless\npackage common             // meaningless\npackage helpers            // meaningless\n\n// In user package:\ntype UserService struct{}  // stutters: user.UserService\ntype UserRepository struct{} // stutters: user.UserRepository\n```\n\n```go\n// GOOD -- clean package names\npackage user\n\ntype Service struct{}     // user.Service -- reads naturally\ntype Repository struct{}  // user.Repository -- no stutter\n\npackage auth\ntype Token struct{}       // auth.Token\ntype Middleware struct{}   // auth.Middleware\n\npackage order\ntype Processor struct{}   // order.Processor\n```\n\n## Struct Field Ordering\n\nStruct fields are ordered by purpose, not alphabetically. Exported fields come first. Related fields are grouped. Groups are separated by blank lines.\n\n```go\n// BAD -- no logical grouping\ntype Server struct {\n    addr          string\n    logger        *slog.Logger\n    TLS           bool\n    db            *sql.DB\n    Port          int\n    Host          string\n    readTimeout   time.Duration\n    cache         *Cache\n    WriteTimeout  time.Duration\n    maxConns      int\n}\n```\n\n```go\n// GOOD -- grouped by purpose, exported first within groups, tags consistent\ntype Server struct {\n    // Configuration (exported)\n    Host         string        `json:\"host\" yaml:\"host\"`\n    Port         int           `json:\"port\" yaml:\"port\"`\n    TLS          bool          `json:\"tls\"  yaml:\"tls\"`\n    ReadTimeout  time.Duration `json:\"read_timeout\" yaml:\"read_timeout\"`\n    WriteTimeout time.Duration `json:\"write_timeout\" yaml:\"write_timeout\"`\n    MaxConns     int           `json:\"max_conns\" yaml:\"max_conns\"`\n\n    // Dependencies (unexported)\n    db     *sql.DB\n    cache  *Cache\n    logger *slog.Logger\n}\n```\n\n**Rules:**\n1. Group by purpose (configuration, dependencies, state, internal)\n2. Exported fields first within each group\n3. Blank lines between field groups\n4. Struct tags are consistent in style (`json:\"snake_case\"`)\n5. Align struct tags only if the struct is small (5 fields or fewer) and will not change often\n\n## Receiver Naming\n\nReceivers are one or two lowercase letters derived from the type name. They are consistent across ALL methods on the type. Never use `this` or `self`.\n\n```go\n// BAD -- inconsistent receivers, verbose names\nfunc (server *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (this *Server) Addr() string { ... }\nfunc (self *Server) IsRunning() bool { ... }\n```\n\n```go\n// GOOD -- consistent single-letter receiver\nfunc (s *Server) Start() error { ... }\nfunc (s *Server) Stop() error { ... }\nfunc (s *Server) Addr() string { ... }\nfunc (s *Server) IsRunning() bool { ... }\n```\n\n| Type | Receiver |\n|------|----------|\n| `Server` | `s` |\n| `User` | `u` |\n| `OrderProcessor` | `op` |\n| `ConfigService` | `cs` |\n| `Handler` | `h` |\n| `Client` | `c` |\n| `Repository` | `r` |\n\nTwo-letter receivers are for when the single letter would be ambiguous (multiple types starting with the same letter in the same file).\n\n## Context: Always First Parameter\n\n`context.Context` is always the first parameter. Always named `ctx`. Never stored in a struct.\n\n```go\n// BAD -- context in wrong position\nfunc GetUser(id string, ctx context.Context) (*User, error) { ... }\n\n// BAD -- context stored in struct\ntype Service struct {\n    ctx context.Context\n    db  *sql.DB\n}\n\n// BAD -- context not named ctx\nfunc GetUser(c context.Context, id string) (*User, error) { ... }\n```\n\n```go\n// GOOD -- context first, named ctx\nfunc GetUser(ctx context.Context, id string) (*User, error) { ... }\n\nfunc (s *Service) ProcessOrder(ctx context.Context, order *Order) error { ... }\n```\n\n## golangci-lint Configuration\n\n```yaml\n# .golangci.yml\nlinters:\n  enable:\n    - errcheck       # unchecked errors\n    - gosimple       # simplifications\n    - govet          # suspicious constructs\n    - ineffassign    # unused assignments\n    - staticcheck    # advanced static analysis\n    - unused         # unused code\n    - gocritic       # opinionated checks\n    - revive         # flexible linter\n    - errorlint      # error wrapping correctness\n    - nilerr         # returning nil when err is not nil\n    - wrapcheck      # errors from external packages are wrapped\n    - prealloc       # slice pre-allocation\n    - unconvert      # unnecessary type conversions\n    - tenv           # os.Setenv in tests (use t.Setenv)\n\nlinters-settings:\n  gocritic:\n    enabled-checks:\n      - appendAssign\n      - argOrder\n      - badCall\n      - badCond\n      - dupArg\n      - dupBranchBody\n      - dupCase\n      - elseif\n      - flagDeref\n      - nilValReturn\n      - singleCaseSwitch\n      - underef\n      - unnecessaryBlock\n\n  revive:\n    rules:\n      - name: blank-imports\n      - name: context-as-argument\n      - name: context-keys-type\n      - name: dot-imports\n      - name: error-naming\n      - name: error-return\n      - name: error-strings\n      - name: exported\n      - name: increment-decrement\n      - name: indent-error-flow\n      - name: package-comments\n      - name: range\n      - name: receiver-naming\n      - name: time-naming\n      - name: var-naming\n      - name: unexported-return\n\n  errorlint:\n    errorf: true          # check fmt.Errorf uses %w\n    asserts: true         # check errors.As uses pointer\n    comparison: true      # check errors.Is instead of ==\n```\n\n## Examples\n\nWorking implementations in `examples/`:\n- **`examples/error-wrapping.md`** -- Complete Go examples showing proper error wrapping with `%w`, error message conventions, sentinel errors, and custom error types with `errors.Is`/`errors.As` patterns\n- **`examples/interface-design.md`** -- Go examples showing interface design patterns: accept interfaces/return structs, small interfaces, consumer-side definition, and the Verb+er naming convention\n\n## Review Checklist\n\nWhen reviewing Go code:\n\n- [ ] All error wrapping uses `fmt.Errorf(\"context: %w\", err)` -- never `%s` or `%v` for errors\n- [ ] Error messages start with lowercase, no \"failed to\" or \"error\" prefix, include identifying info (IDs, paths)\n- [ ] Sentinel errors use `Err` prefix with PascalCase: `ErrNotFound`, `ErrInvalidInput`\n- [ ] Functions accept interfaces and return concrete structs\n- [ ] Interfaces are small (1-3 methods) and defined at the consumer side\n- [ ] Single-method interfaces follow Verb+er naming (`Reader`, `Writer`, `Validator`)\n- [ ] Package names are short, lowercase, single word -- no `utils`, `helpers`, `common`\n- [ ] No name stuttering: `user.Service` not `user.UserService`\n- [ ] Struct fields are grouped by purpose with exported fields first and blank lines between groups\n- [ ] Struct tags are consistent in style (`json:\"snake_case\"`)\n- [ ] Receiver names are 1-2 lowercase letters derived from the type name, consistent across all methods\n- [ ] No `this` or `self` receivers\n- [ ] `context.Context` is always the first parameter, always named `ctx`, never stored in a struct\n- [ ] `golangci-lint` is configured with at minimum: errcheck, gosimple, govet, staticcheck, unused, gocritic, revive, errorlint\n\n<!-- File: .claude/skills/simple-english/references/rule-catalog.md (Tokens: ~2676 | Category: anthropic-skill) -->\n# Rule catalog: ASD-STE100 Issue 9 for software text\n\nRead this file for CHECK mode, for Strict mode, or when a rule number is in question. The core skill in `SKILL.md` names the rules that move output the most. This file holds the whole catalog.\n\n53 rules in 9 sections, paraphrased from ASD-STE100 Issue 9 with software examples. Rules marked (S) are Strict mode only (see `references/strict-vocabulary.md`). The official wording is in the free standard at asd-ste100.org.\n\n### Section 1 — Words (Rules 1.1-1.14)\n\n| Rule | Instruction |\n|---|---|\n| 1.1-1.4, 1.6 (S) | Use only approved words, as their listed part of speech, meaning, and form. |\n| 1.5 | You can use domain words as technical nouns (\"webhook\", \"commit\", \"endpoint\"). |\n| 1.7 | Do not use technical nouns as verbs. |\n| 1.8 | Use the technical nouns of your project or industry. |\n| 1.9 | When you pick a technical noun, pick a short and clear one. |\n| 1.10 | No regional, slang, or jargon words as technical nouns. |\n| 1.11 | One item, one name. Do not call it \"config\" here and \"settings\" there. |\n| 1.12 | You can use domain verbs as technical verbs (\"deploy\", \"compile\", \"merge\"). The standard names computer verbs as legal: click, type, copy, paste, delete, save, install, download, update, and more. When a common verb does the same job, prefer it: \"find\" instead of \"detect\". |\n| 1.13 | Do not use technical verbs as nouns. |\n| 1.14 | Use American English spelling. |\n\nIn Plain mode, rules 1.5, 1.8, and 1.12 make your domain vocabulary legal. The ones agents break are 1.7, 1.11, and 1.13.\n\n**Before:** You can webhook the event, then do a deploy.\n**After:** Send the event to the webhook. Then deploy the service.\n\n### Section 2 — Multi-word nouns (Rules 2.1-2.2)\n\n| Rule | Instruction |\n|---|---|\n| 2.1 | Write multi-word nouns of three words or fewer. |\n| 2.2 | When a technical noun needs more than three words, write it in full once, then give a short form or hyphenate the units. |\n\nBreak long noun chains with prepositions (of, on, in, for):\n\n**Before:** the connection pool timeout configuration value\n**After:** the timeout value for the connection pool\n\n### Section 3 — Verbs (Rules 3.1-3.7)\n\n| Rule | Instruction |\n|---|---|\n| 3.1 (S) | Use only the verb forms that the dictionary gives. |\n| 3.2 | Use only: infinitive, imperative, simple present, simple past, simple future, past participle as adjective. |\n| 3.3 | Use the past participle only as an adjective (\"the cached response\"). |\n| 3.4 | No auxiliary verbs for complex constructions. No present perfect, no \"is to be installed\". |\n| 3.5 | Use an \"-ing\" form only as a technical noun or inside one (\"logging\", \"the mounting bracket\"), never as a verb. |\n| 3.6 | Active voice. In descriptive text, passive is legal only when the agent is unknown. To repair an agentless passive, use \"you\" (the reader) or \"we\" (your company): \"Indexes are not used on this table\" → \"We do not use indexes on this table.\" |\n| 3.7 | Describe an action with a verb, not a noun (\"compress the file\", not \"perform compression of the file\"). |\n\n**Approved modals: can, will, must. Banned: should, would, may, might, could.**\nThe modal ladder below routes each banned modal. This matters double for agent instructions, because models read \"should\" as optional.\n\n### Section 4 — Sentences (Rules 4.1-4.5)\n\n| Rule | Instruction |\n|---|---|\n| 4.1 | Write short and clear sentences. |\n| 4.2 | Do not omit words or use contractions to shorten sentences. Keep articles, keep \"that\". |\n| 4.3 | Use a vertical list for complex text: colon on the lead-in, uppercase start, a period only on full-sentence items, no mixed instructions and facts, no nesting. |\n| 4.4 | Use connecting words between sentences on related topics (\"Then\", \"As a result\"). |\n| 4.5 | Put an article (the, a, an) or a demonstrative adjective (this, these) before nouns where applicable. Exception: no article before a noun when an identifier follows it: \"Restart pod web-7f9b2\". |\n\nRule 4.2 is the anti-terseness rule. Plain English is short sentences with complete grammar, not telegraph style:\n\n**Wrong shortening:** Ensure file exists before running.\n**Plain:** Make sure that the file exists before you run the command.\n\n### Section 5 — Procedural writing (Rules 5.1-5.5)\n\n| Rule | Instruction |\n|---|---|\n| 5.1 | Maximum 20 words per sentence. Warnings and cautions included. |\n| 5.2 | One instruction per sentence, unless two actions happen at the same time. A step can add one sentence for an immediate result or limit. |\n| 5.3 | Write instructions in the imperative: \"Run the migration.\" |\n| 5.4 | Put a required condition before the command, divided by a comma: \"If the build fails, read the log.\" |\n| 5.5 | Notes give information, never instructions or limits. A limit belongs with its action. Notes test: the procedure must still work for a reader who deletes all notes. |\n\n**Before:** You'll want to grab the API key from the dashboard before configuring the client, which you can do under Settings.\n**After:** Get the API key from the dashboard, under Settings. Then configure the client with this key.\n\n### Section 6 — Descriptive writing (Rules 6.1-6.6)\n\n| Rule | Instruction |\n|---|---|\n| 6.1 | Give information gradually: one new fact per sentence. |\n| 6.2 | Use key words and phrases to give the text a logical structure. |\n| 6.3 | Maximum 25 words per sentence. |\n| 6.4 | Group related information in paragraphs. |\n| 6.5 | One topic per paragraph. |\n| 6.6 | Maximum six sentences per paragraph. |\n\n### Section 7 — Safety instructions (Rules 7.1-7.3)\n\n| Rule | Instruction |\n|---|---|\n| 7.1 | Use a word that shows the risk level (\"WARNING\" = injury, \"CAUTION\" = damage). If the two risks occur together, use \"WARNING\". |\n| 7.2 | Start with a clear command or condition. |\n| 7.3 | Then give the risk or the possible result. |\n\nNever bury the instruction after the explanation. The same pattern fits destructive CLI flags and irreversible migrations.\n\n**Before:** Note that data loss may occur in some circumstances if the destructive flag happens to be enabled when running against production.\n**After:** CAUTION: Do not use the `--force` flag against production. The flag deletes rows that do not match the source.\n\n### Section 8 — Punctuation and word count (Rules 8.1-8.7)\n\n| Rule | Instruction |\n|---|---|\n| 8.1 | All standard punctuation is legal except the semicolon. Write two sentences instead. |\n| 8.2 | Use hyphens to connect words that act as one unit. |\n| 8.3 | Parentheses are legal for references, item numbers, abbreviations, plural forms, explanations, alternatives. |\n| 8.4 | In a vertical list, the lead-in colon ends a sentence for word count. Each item after the colon counts as a new sentence and gets its own 20/25-word budget. |\n| 8.5-8.7 | Count as one word each: text in parentheses, a hyphenated word, numbers, numbers with units, abbreviations, identifiers, quoted text, titles, labels, proper nouns. |\n\nRule 8.6 matters for software text: `sqlpipe run --config sqlpipe.yaml` in backticks counts as one word.\n\n**Dashes** (this skill, not the standard). An em-dash (`—`) splices two statements and hides the logic between them. Name the relation (\"because\", \"but\", \"for example\") or write two sentences. A spaced or double hyphen between statements is the same dash. A range (`5–10`), a list marker, and a flag (`--force`) are not.\n\n### Section 9 — Writing practices (Rules 9.1-9.4, GR-1 to GR-8)\n\n| Rule | Instruction |\n|---|---|\n| 9.1 | When a word-for-word replacement does not work, restructure the sentence. |\n| 9.2 (S) | Use each approved word correctly: approved meaning, approved part of speech. |\n| 9.3 | Prefer the one-word verb over the phrasal verb (\"decrease\", not \"go down\"; \"install\", not \"set up\"). Strict mode: the phrasal verb is a violation. |\n| 9.4 | Keep one consistent style and terminology through the whole document. |\n\nGeneral recommendations: keep \"that\" (GR-1), primary verb first and the tool after \"with\" (GR-2: \"Fetch the URL with curl\"), clear pronoun referents (GR-3), \"this + noun\" (GR-4), inclusive language (GR-7). GR-6: \"e.g.\" → \"for example\", \"i.e.\" → \"that is\", delete \"etc.\" and name the items.\n\n### The modal ladder\n\n| You wrote | Write instead |\n|---|---|\n| should (requirement) | must |\n| should (recommendation) | Delete it, or state it as fact: \"X is better because Y.\" |\n| should (inverted conditional: \"should a failure occur\") | if: \"If a failure occurs\" |\n| may / might / could (possibility) | can |\n| may (permission) | can |\n| would (hypothetical) | can, or restructure: \"If X occurs, Y occurs.\" |\n\n## Signs of AI Writing\n\nAI text drifts in known directions (Wikipedia \"Signs of AI writing\"). The rules above remove some already. Guard against the rest by direction, in documents and replies alike:\n\n- Inflated significance: no \"vital\", \"crucial\", \"a testament\". State the fact.\n- Negative parallelism: no \"not just X, it is Y\".\n- Rule of three: no decorative triplets.\n- Vague attribution: no \"studies show\". Name the source, or drop the claim.\n- False ranges: no \"ranging from X to Y\" without real limits.\n- Restating summaries: no \"in conclusion\" paragraphs.\n- Editorializing asides: no \"it is important to note\".\n- Collaborative leftovers: no \"I hope this helps\", no \"Let me know\".\n- Formatting habits: no bold as decoration, no bold lead-ins, no emoji as structure, no heading for two sentences.\n\nFor the specific overused words, `references/word-swaps.md` maps each one to a plain replacement. If a word carries no fact, delete it instead.\n\n## Word Choice\n\nOne word, one meaning, one part of speech, for the whole document (Rules 1.11, 9.4).\n\n- The settings file is `configuration`, never config, settings, or options in the same document.\n- The verify concept is `make sure that`, never check, verify, confirm, validate, or ensure as verbs. Strict mode routes the rest with `references/strict-vocabulary.md`.\n- Common swaps: however → but, therefore → as a result, since (= because) → because, perform → do, avoid → prevent, repeat → do again, acceptable → permitted, now → delete it.\n\n<!-- File: .claude/skills/simple-english/references/strict-vocabulary.md (Tokens: ~1254 | Category: anthropic-skill) -->\n# Strict mode: the dictionary discipline\n\nRead this file when the user names STE, ASD-STE100, or compliance. Strict mode adds the dictionary rules to the document. It does not change the reply to the user, which stays in Plain mode.\n\nThe official dictionary (about 900 approved words and 1,200 rejected words with their alternatives) is copyrighted by ASD and is not reproduced here. This file gives the rules that depend on it, the rulings that software writers meet most, and the words the standard names as recurring errors. Tell the user, one time per conversation and in one sentence, that this index is lossy and that full compliance needs the official dictionary (free at asd-ste100.org).\n\n## Rules that exist only with the dictionary\n\n| Rule | Instruction |\n|---|---|\n| 1.1 | Use only approved words, technical nouns, or technical verbs. |\n| 1.2 | Use an approved word only as its listed part of speech. |\n| 1.3 | Use an approved word only with its approved meaning. |\n| 1.4 | Use only the approved forms of verbs and adjectives. |\n| 1.6 | Use an unapproved word only when it is a technical noun or part of one. |\n| 3.1 | Use only the verb forms that the dictionary gives. |\n| 9.2 | Use each approved word correctly: approved meaning, approved part of speech. |\n| GR-5 | Avoid false friends: words that look like a word in the reader's language but mean something else. |\n| GR-8 | Use the possessive apostrophe only when you are sure that it is correct. If unsure, write \"the file of the user\". |\n\nIssue 9 adds a quick-reference list of approved verbs in the dictionary introduction. Check your verbs against it.\n\n## Part-of-speech rulings\n\n| Word | Ruling |\n|---|---|\n| test, check, work | Noun only. \"Do a test\", not \"test the pump\". \"Check that X\" becomes \"make sure that X\". |\n| oil | Technical noun only. For the verb, the dictionary gives \"lubricate\": \"Lubricate the linkage with oil.\" |\n| help | Verb only. For the noun, the dictionary gives \"aid\": \"with the aid of\". |\n| fall (noun) | Rejected. Use \"decrease\" for a reduction in value. Use \"fall\" (verb) only for movement downward by gravity: \"Make sure that the tools do not fall into the engine.\" |\n| follow | \"To come after\" only, never \"obey\". Write \"obey the instructions\". |\n| above, below | Physical positions only. For limits write \"more than\", \"less than\". |\n\n## Dictionary rulings on common software verbs\n\nThe standard has already chosen. Use the approved word.\n\n| You wrote | Dictionary status | Use instead |\n|---|---|---|\n| check (verb), verify, confirm, ensure | All rejected as verbs | Route by intent: `make sure that` (a state), `examine` (look for faults: \"examine the log\"), `measure` (get a value), or the noun: \"do a check of\". |\n| validate | Not in the dictionary | Legal as a technical verb (Rule 1.12), or replace with `make sure that`. |\n| delete, drop (verb), destroy | All rejected as dictionary verbs | `erase` (data), `remove` (physical). In computer contexts `delete` is also a legal technical verb (Rule 1.12). Do not use `drop` or `destroy`. |\n| remove | Approved verb | Keep it. |\n| run, execute | Both rejected | `operate` for run, `do` for execute. |\n| invoke, launch | Not in the dictionary | Legal as technical verbs (Rule 1.12). |\n| display (verb), render, present (verb) | All rejected | `show` covers most software cases. Official alternatives: display → `show`, render → `make`, present → `give` or `show`. |\n| issue | Not in the dictionary | Use as a technical noun, or replace with `problem` (approved). |\n| failure | Rejected in general use; approved as a technical noun for performance loss | Use only for a performance error: \"a failure of the pump\". |\n| error, problem | Approved nouns | Keep them. |\n\n## Recurring errors the standard names\n\nThe dictionary introduction lists the words that writers get wrong most often. This is the software-relevant set, as rulings only.\n\n| You wrote | STE writes |\n|---|---|\n| however | but |\n| therefore | thus, as a result |\n| since (= because) | because |\n| any | Delete it, or restructure: \"if you have any questions\" → \"if you have questions\" |\n| now | at this time. Better, delete it: \"now start the service\" → \"start the service\" |\n| need to, have to | Imperative in procedures (\"install\"); \"it is necessary to\" in descriptive text |\n| perform | do |\n| insert | put (but SQL `INSERT` stays: it is quoted text) |\n| reach | get, get to |\n| avoid | prevent |\n| repeat | do … again |\n| acceptable | permitted. Better, give the limit: \"a latency of less than 200 ms\" |\n| complete (adjective) | completed |\n| the example below, the section above | Name the target, or put the reference after it: \"the example that follows\" |\n\n## Strict self-check\n\nAdd these two steps to the self-check in SKILL.md:\n\n1. Search the draft for every verb in the two tables above. Replace each hit with the approved word.\n2. Search for the phrasal verbs you built (\"set up\", \"go down\"). Replace each with the one-word verb (Rule 9.3: \"install\", \"decrease\").\n\n<!-- File: .claude/skills/simple-english/references/use-cases.md (Tokens: ~832 | Category: anthropic-skill) -->\n# Use cases beyond documentation\n\nSTE was built for aircraft maintenance manuals. The same properties transfer to any text where a misreading has a cost: one meaning per word, short sentences, condition-first commands. Each case below names the mode and the adaptations.\n\n## Error messages and CLI output\n\nMode: procedural. An error message is an instruction to a stressed reader at 2 a.m., so it is the highest-value target.\n\nPattern: state what happened (simple past), state the cause if known, give the command or condition that fixes it.\n\n> Before: Oops! Something went wrong while attempting to establish a connection. Please ensure your credentials are properly configured and try again.\n> After: Connection to the database failed. The password for user `app` was not correct. Set `DB_PASSWORD` and connect again.\n\n## Runbooks and standard operating procedures\n\nMode: procedural, with the 20-word limit enforced hard. An on-call runbook is a maintenance manual, which is what STE was made for.\n\n- Every step is imperative, one instruction per step, condition first.\n- A warning comes before its step: command first, risk second.\n- An operator under pager stress reads each sentence once, so the 20-word limit is not negotiable.\n\n## Incident reports and postmortems\n\nMode: descriptive, simple past only. A timeline in present perfect (\"we have identified\") hides when things happened.\n\n> Before: We have identified an issue that may have impacted some users' ability to access the service.\n> After: Between 14:02 and 14:31 UTC, 12% of requests failed. A deploy at 14:00 removed the cache warmup step.\n\nSTE bans hedges such as \"may have impacted\". The report states what is known and says \"unknown\" for the rest. It reads more honest because it is.\n\n## Commit messages and PR descriptions\n\nMode: imperative subject line, descriptive body. The convention already matches STE. Apply the word swaps and the 25-word limit to the body. Delete \"this PR aims to\".\n\n## API changelogs and release notes\n\nMode: descriptive. One entry, one change, one sentence where possible. A \"Breaking:\" entry follows the warning pattern, command first: \"Update your calls to `v2/users`. The `name` field split into `first_name` and `last_name`.\"\n\n## Instructions for AI agents (prompts, AGENTS.md, skills)\n\nMode: procedural. A system prompt is a procedure for a reader that cannot ask questions, which is the exact reader STE was designed for.\n\n- One instruction per sentence keeps each rule quotable and hard to half-follow.\n- One word, one meaning stops the model from treating \"check\", \"verify\", and \"validate\" as three operations.\n- A condition first (\"If the build fails, stop\") beats a trailing condition, which models drop.\n- No \"should\". A model reads \"should\" as optional. Write \"must\" or delete the rule.\n\n## Support macros and status-page updates\n\nMode: descriptive, 25-word limit. Non-native readers are the majority of many user bases. Not \"we sincerely apologize for any inconvenience this may have caused\" but \"The API was down for 18 minutes. Uploads made during this time were saved and will process today.\"\n\n## Translation and localization prep\n\nMode: strict. The original purpose of STE was English that non-native maintenance crews can read, and it doubles as pre-editing for machine translation. One meaning per word plus complete grammar (articles, \"that\") removes most translation ambiguity. If your docs get localized, STE cuts the error rate and the cost.\n\n## UI copy and empty states\n\nMode: procedural, hard length limits. Buttons and labels are technical names and are exempt. Body copy follows the rules: \"No projects yet. Create a project to start.\"\n\n## Where STE does not fit\n\nMarketing pages, launch posts, blog voice, brand writing. STE deletes persuasion on purpose. Write those in your own voice. Then use STE for the docs that the landing page links to.\n\n<!-- File: .claude/skills/simple-english/references/word-swaps.md (Tokens: ~630 | Category: anthropic-skill) -->\n# Slop-to-simple substitutions\n\nThis table is ours, not the ASD dictionary. It maps the words AI-generated docs overuse to plain replacements. If the word carries no fact, delete it instead of replacing it.\n\n| Slop | Write instead |\n|---|---|\n| leverage, utilize | use |\n| in order to | to |\n| prior to | before |\n| ensure | make sure that |\n| it is worth noting that | (delete) |\n| it's important to | (delete — state the fact) |\n| simply, just, easily, seamless, seamlessly, effortlessly | (delete) |\n| robust, powerful, comprehensive, performant | (delete, or give the measurable property) |\n| functionality | function, feature |\n| enables you to, allows you to | you can |\n| is designed to, aims to | (delete — say what it does) |\n| facilitate | help, make possible |\n| dive into, delve into | read, examine |\n| when it comes to | for |\n| in the event that | if |\n| due to the fact that | because |\n| as needed, as necessary | (state the condition) |\n| and/or | Pick one, or write \"X, or Y, or both\" |\n| e.g. / i.e. / etc. | for example / that is / (name the items) |\n| gracefully handles | (say what it does: \"retries three times, then stops\") |\n| out of the box | by default |\n| under the hood | internally |\n| blazingly fast | fast (give the number) / (delete) |\n| streamline | make simpler, make faster |\n| plethora, myriad | many |\n| addresses the issue, tackles | corrects the fault, removes the error |\n| pivotal, crucial, crucially, paramount | important |\n| tapestry, testament, synergy | (delete) |\n| interplay | interaction (or delete) |\n| intricate | complex |\n| vibrant, nuanced, multifaceted | (delete, or name the parts) |\n| realm, landscape (metaphorical) | area |\n| groundbreaking, cutting-edge, state-of-the-art, innovative, unprecedented | new (or delete) |\n| transformative, game-changer | (delete — say what changes) |\n| revolutionize | change |\n| showcase, underscore, emphasize | show |\n| foster, empower, bolster | help, support, let |\n| harness | use |\n| enhance | improve |\n| elevate | increase |\n| furthermore, moreover | also |\n| in conclusion, in summary, at the end of the day | (delete) |\n| embark, endeavor | start, try |\n| meticulous, meticulously | careful, carefully |\n| holistic | full |\n| paradigm | model |\n| navigate (metaphorical) | go to |\n| boasts | has |\n| nestled, in the heart of | (delete — give the location or the fact) |\n| bustling | busy |\n| that being said, notwithstanding | but |\n| I hope this helps, let's dive in | (delete) |\n\n<!-- File: .claude/skills/simple-english/SKILL.md (Tokens: ~1882 | Category: anthropic-skill) -->\n---\nname: simple-english\ndescription: |\n  Write or rewrite text in plain, layman-readable English in the spirit of\n  ASD-STE100 Simplified Technical English: short sentences, active voice,\n  simple tenses, one word one meaning, condition before command, every\n  technical term defined at first use, no AI slop. Default mode is Plain.\n  Strict mode applies full STE vocabulary compliance when the user names\n  STE, ASD-STE100, or compliance. Use for documentation, READMEs, runbooks,\n  procedures, error messages, release notes, incident reports, API guides,\n  and explanations for readers outside the field. Also use when the user\n  says \"STE\", \"Simplified Technical English\", \"ASD-STE100\", \"plain English\",\n  \"layman's terms\", \"explain it simply\", \"no jargon\", \"de-slop\", \"make this\n  readable\", \"write for non-native readers\", or asks for docs that translate\n  well. The same rules govern the reply: answer first, prose only.\nlicense: MIT\ncompatibility: claude-code cursor codex gemini-cli opencode\nmetadata:\n  version: \"2.1.0\"\n  standard: ASD-STE100 Issue 9 (2025-01-15)\n---\n\n# Simple English\n\nWrite plain English that a smart reader outside your field understands on one read. The rules come from ASD-STE100, the controlled language aerospace uses so a tired mechanic cannot misread an instruction. Two registers exist: the document you write or rewrite, and the reply you type in chat. Each has its own short rule set below. Nothing else in this file is optional.\n\n## The Document\n\nWhen asked to write or rewrite documentation, apply these rules to the prose:\n\n1. **Classify each passage.** Procedural text tells the reader what to do: imperative mood, 20 words per sentence, one instruction per sentence. Descriptive text explains: simple tenses, 25 words per sentence, one topic per paragraph, six sentences per paragraph at most.\n2. **Never touch** code, identifiers, commands, flags, file paths, quoted errors, product names, or facts. When the source gives no number or cause, keep the general statement.\n3. **Condition before command, with a comma.** \"If the build fails, read the log.\"\n4. **Simple tenses, active voice.** No present perfect (\"has completed\" → \"completed\"). No \"-ing\" verb after a comma (\", making it easy\" → new sentence). Name the actor: \"You run the migration.\"\n5. **Modals: can, will, must.** Never should, would, may, might, could. A required \"should\" becomes \"must\". An optional one is deleted.\n6. **Complete grammar.** No contractions, keep articles, keep \"that\". Short sentences, not telegraph style.\n7. **No semicolons and no em-dashes.** Write two sentences, or name the relation.\n8. **One word, one meaning, for the whole document.** Use `make sure that` for check, verify, confirm, validate, ensure. Use `configuration` for config, settings, options. Break noun chains over three words with a preposition (\"the timeout value for the connection pool\").\n9. **State what the reader needs before you name the action.** Define a concept term at its first use, under ten words, one per sentence. Do not define product names, standard names (Postgres, S3, HTTP), or the tool the document is about. The same rule covers a fact, not just a word: name the host, the flag, or the prior step that a command depends on, instead of assuming the reader already has it. \"Restart the service\" becomes \"Restart the `sync` service on the host that runs the job.\"\n10. **State the fact, not its importance.** Delete words that carry no fact: simply, seamlessly, robust, powerful, comprehensive, leverage, crucial, \"in order to\", \"it is worth noting\". No \"not just X, it is Y\". No decorative triplets. No \"in conclusion\".\n11. **Format for the eye, not for decoration.** No bold lead-ins, no bold as emphasis, no emoji, no heading over two sentences. A vertical list is for three or more parallel items or steps: colon on the lead-in, uppercase start, one instruction per item.\n12. **Warnings: command or condition first, then the risk.** \"Do not run this against production. The command deletes rows.\"\n\nUse American spelling. `references/word-swaps.md` maps the overused words to plain ones. For an error message, a runbook, an incident report, release notes, a commit message, or UI copy, read `references/use-cases.md` first: it names the mode and the pattern for each.\n\n**Before (real AI output):**\n\n> **Connection timeouts.** If sqlpipe hangs or fails with `dial tcp: i/o timeout`, check that the host running sqlpipe can reach the Postgres port (usually 5432) — this is often a security group or firewall rule blocking the connection. If you're connecting to a managed database (RDS, Cloud SQL, etc.), confirm the instance allows connections from sqlpipe's IP.\n\n**After (procedural, headed, numbered):**\n\n> ## Connection timeouts\n>\n> sqlpipe stops with `dial tcp: i/o timeout` when it cannot connect to the Postgres port (5432 by default).\n>\n> 1. Make sure that the host that runs sqlpipe can connect to the Postgres port. A firewall or security group usually blocks it.\n> 2. If the database is managed (RDS, Cloud SQL), make sure that the instance accepts connections from the IP of sqlpipe.\n\n## The Reply\n\nEvery chat reply, in every mode, follows these rules. Read them last, apply them first:\n\n1. Answer in prose. No headers, no bullet lists, no bold, no tables. A code block is legal when the reader must copy it.\n2. The first sentence gives the answer or the result. Do not restate the question.\n3. No em-dashes. Name the relation (\"because\", \"but\", \"for example\") or write two sentences.\n4. Define a concept term in a few words the first time you use it: \"idempotent (safe to run twice)\". Do not define product names.\n5. No contractions. No openers (\"Certainly\", \"Great question\") and no closers (\"I hope this helps\", \"Let me know\").\n6. Do not shorten quoted error text, security warnings, or confirmations before a destructive action.\n\n**Before:** The failure stems from control-plane leader election during pod churn — nothing to worry about!\n**After:** The pods restarted and the queue lost its leader for a short time. It recovered without help. You do not have to do anything.\n\n## Self-Check Before You Deliver\n\n1. Reply: search for `—`, `**`, `#`, and a line that starts with `-`. Remove each one.\n2. Document: count the words in your three longest sentences. Over 20 or 25, split. Search for `'`, `has been`, `should`, `may`, `;`, `—`, `, making`, `**`, `check`, `verify`, `config`, and any heading that covers fewer than three sentences. Fix each hit. Read each step: does it name a host, a flag, or a prior step the reader must already have? If not, add it or point to it.\n\n## Modes\n\n**Plain** is the default and is all of the above. **Strict** applies when the user names STE, ASD-STE100, or compliance: read `references/strict-vocabulary.md` before you draft the document, and say once that no tool guarantees compliance. The reply stays Plain in every mode.\n\nWhen asked to CHECK text instead of writing it, first open `references/rule-catalog.md`. Then report each violation as: rule number quoted from that file, the offending text, a compliant rewrite. Never cite a rule number from memory. When the user asked for compliance, end with one sentence: no tool can guarantee ASD-STE100 compliance, and the standard is a free download at asd-ste100.org.\n\n## Limits\n\nThese rules are for facts and instructions, not marketing copy or brand writing: they delete persuasion by design. Say so, and offer them for the docs instead.\n\n## References\n\n- `references/rule-catalog.md` — the 53 rules of Issue 9 with software examples, for CHECK mode\n- `references/strict-vocabulary.md` — the dictionary discipline for Strict mode\n- `references/word-swaps.md` — slop-to-plain word map\n- `references/use-cases.md` — mode and pattern for error messages, runbooks, incident reports, release notes, commits, agent prompts, UI copy, translation prep\n</yakaai_skills>"}