docs: add AGENTS.python/go/typescript.md and pre-push check rules
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
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
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>
This commit is contained in:
227
.claude/AGENTS.go.md
Normal file
227
.claude/AGENTS.go.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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.**
|
||||
|
||||
```bash
|
||||
# 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:**
|
||||
```bash
|
||||
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:
|
||||
|
||||
```yaml
|
||||
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:**
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
// 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:**
|
||||
```go
|
||||
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:**
|
||||
```go
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```go
|
||||
// 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 |
|
||||
Reference in New Issue
Block a user