Files
breakpilot-core/.claude/AGENTS.go.md
Sharang Parnerkar e3b33ef596
Some checks failed
CI / go-lint (push) Has been cancelled
CI / python-lint (push) Has been cancelled
CI / nodejs-lint (push) Has been cancelled
CI / test-go-consent (push) Has been cancelled
CI / test-python-voice (push) Has been cancelled
CI / test-bqas (push) Has been cancelled
docs: add AGENTS.python/go/typescript.md and pre-push check rules
Mandatory pre-push gates for all three language stacks with exact
commands, common pitfalls, and architecture rules. CLAUDE.md updated
with quick-reference section linking to the new files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 08:35:12 +02:00

6.6 KiB

AGENTS.go.md — Go Agent Rules

Applies to: ai-compliance-sdk/ (Go/Gin service)


NON-NEGOTIABLE: Pre-Push Checklist

BEFORE every git push, run ALL of the following from the module root. A single failure blocks the push.

# 1. Format (gofmt is non-negotiable — unformatted code fails CI)
gofmt -l . | grep -q . && echo "FORMATTING ERRORS — run: gofmt -w ." && exit 1 || true

# 2. Vet (catches suspicious code that compiles but is likely wrong)
go vet ./...

# 3. Lint (golangci-lint aggregates 50+ linters — the de-facto standard)
golangci-lint run --timeout=5m ./...

# 4. Tests with race detector
go test -race -count=1 ./...

# 5. Build verification (catches import errors, missing implementations)
go build ./...

One-liner pre-push gate:

gofmt -l . | grep -q . && exit 1; go vet ./... && golangci-lint run --timeout=5m && go test -race -count=1 ./... && go build ./...

Why each check matters

Check Catches Time
gofmt Formatting violations (CI rejects unformatted code) <1s
go vet Printf format mismatches, unreachable code, shadowed vars <5s
golangci-lint 50+ static analysis checks (errcheck, staticcheck, etc.) 10-30s
go test -race Race conditions (invisible without this flag) 10-60s
go build Import errors, interface mismatches <5s

golangci-lint Configuration

Config lives in .golangci.yml at the repo root. Minimum required linters:

linters:
  enable:
    - errcheck       # unchecked errors are bugs
    - gosimple       # code simplification
    - govet          # go vet findings
    - ineffassign    # useless assignments
    - staticcheck    # advanced static analysis (SA*, S*, QF*)
    - unused         # unused code
    - gofmt          # formatting
    - goimports      # import organization
    - gocritic       # opinionated style checks
    - noctx          # HTTP requests without context
    - bodyclose      # unclosed HTTP response bodies
    - exhaustive     # exhaustive switch on enums
    - wrapcheck      # errors from external packages must be wrapped

linters-settings:
  errcheck:
    check-blank: true    # blank identifier for errors is a bug
  govet:
    enable-all: true

issues:
  max-issues-per-linter: 0
  max-same-issues: 0

Never suppress with //nolint: without a comment explaining why it's safe.


Code Structure (Hexagonal Architecture)

ai-compliance-sdk/
├── cmd/
│   └── server/main.go        # thin: parse flags, wire deps, call app.Run()
├── internal/
│   ├── app/                  # dependency wiring
│   ├── domain/               # pure business logic, no framework deps
│   ├── ports/                # interfaces (repositories, external services)
│   ├── adapters/
│   │   ├── http/             # Gin handlers (≤30 LOC per handler)
│   │   ├── postgres/         # DB adapters implementing ports
│   │   └── external/         # third-party API clients
│   └── services/             # orchestration between domain + ports
└── pkg/                      # exported, reusable packages

Handler constraint — max 30 lines per handler:

func (h *RiskHandler) GetRisk(c *gin.Context) {
    id, err := uuid.Parse(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
        return
    }
    risk, err := h.service.Get(c.Request.Context(), id)
    if err != nil {
        h.handleError(c, err)
        return
    }
    c.JSON(http.StatusOK, risk)
}

Error Handling

// REQUIRED: wrap errors with context
if err != nil {
    return fmt.Errorf("get risk %s: %w", id, err)
}

// REQUIRED: define sentinel errors in domain package
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")

// REQUIRED: check errors — never use _ for error returns
result, err := service.Do(ctx, input)
if err != nil {
    // handle it
}

errcheck linter enforces this — zero tolerance for unchecked errors.


Testing Requirements

internal/
├── domain/
│   ├── risk.go
│   └── risk_test.go          # unit: pure functions, no I/O
├── adapters/
│   ├── http/
│   │   ├── handler.go
│   │   └── handler_test.go   # httptest-based, mock service
│   └── postgres/
│       ├── repo.go
│       └── repo_test.go      # integration: testcontainers or real DB

Test naming convention:

func TestRiskService_Get_ReturnsRisk(t *testing.T) {}
func TestRiskService_Get_NotFound_ReturnsError(t *testing.T) {}
func TestRiskService_Get_DBError_WrapsError(t *testing.T) {}

Table-driven tests are mandatory for functions with multiple cases:

func TestValidateInput(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        wantErr bool
    }{
        {"valid", "ok", false},
        {"empty", "", true},
        {"too long", strings.Repeat("x", 300), true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateInput(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
            }
        })
    }
}
# Pre-push: unit tests only (fast)
go test -race -count=1 -run "^TestUnit" ./...

# CI: all tests
go test -race -count=1 -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | grep total

Context Propagation

Every function that does I/O (DB, HTTP, file) must accept and pass context.Context as the first argument:

// REQUIRED
func (r *RiskRepo) Get(ctx context.Context, id uuid.UUID) (*Risk, error) {
    return r.db.QueryRowContext(ctx, query, id).Scan(...)
}

// FORBIDDEN — no context
func (r *RiskRepo) Get(id uuid.UUID) (*Risk, error) { ... }

noctx linter enforces HTTP client context. Manual review required for DB calls.


Common Pitfalls That Break CI

Pitfall Prevention
Unformatted code gofmt -w . before commit
Unchecked error return from rows.Close() / resp.Body.Close() errcheck + bodyclose linters
Goroutine leak (goroutine started but never stopped) -race test flag
Shadowed err variable in nested scope govet -shadow
HTTP response body not closed bodyclose linter
interface{} instead of any (Go 1.18+) gocritic
Missing context on DB/HTTP calls noctx linter
Returning concrete type from constructor instead of interface breaks testability