# CLAUDE.md — Agent Instructions This file is read automatically by Claude Code at the start of every session. Keep it concise — the agent needs signal, not essays. --- ## Project overview Go 1.25 template / PoC starter. Demonstrates: structured logging (slog), config (flag), interfaces + manual fakes, linting (golangci-lint), security scanning (gosec, govulncheck), git hooks, devcontainer, VSCode tasks. Module: `gitea.djmil.dev/djmil/go-template` — update this when you fork. --- ## Project structure ``` cmd/app/main.go composition root — wires deps, no logic here internal/config/ flag-based config loader (config.Load) internal/logger/ slog wrapper with WithField / WithFields internal/greeter/ Example domain package (delete or repurpose) tools.go Tool version pinning (build tag: tools) .golangci.yml Linter rules .githooks/pre-push Runs gofmt + go vet + golangci-lint + gosec before push ``` --- ## Project rules - **Module imports** — always use the full module path `gitea.djmil.dev/djmil/go-template/...` - **Packages** — keep `cmd/` thin (wiring only); business logic belongs in `internal/` - **Interfaces** — define interfaces where they are *used*, not where they are *implemented* - **Errors** — wrap with `fmt.Errorf("context: %w", err)`; never swallow errors silently - **Logging** — use `log.WithField("key", val)` for structured context; never `fmt.Sprintf` in log messages; `log/slog` is the backend - **Config** — all configuration through `internal/config` (flag-parsed); no hard-coded values in logic packages --- ## Code style - Follow `gofmt` + `goimports` formatting (enforced by linter and git hook) - Imports: stdlib → blank line → external → blank line → internal (goimports handles this) - Error variables: `err` for local, `ErrFoo` for package-level sentinels - Constructors: `New(deps...) *Type` pattern - Comment every exported symbol (golangci-lint will warn if missing) - Max line length: 120 chars (configured in `.golangci.yml`) - Prefer explicit over clever; PoC code should be readable first --- ## Testing rules - Tests use only the standard `testing` package — no third-party assertion libraries - Test files: `package foo_test` (black-box) unless white-box access is needed - Fake dependencies with **manual fakes** (implement the interface inline in `_test.go`) - Use `logger.NewNop()` when the test doesn't care about log output - Table-driven tests with `t.Run("description", ...)` for multiple cases - The race detector is enabled in CI (`make test-race`); don't introduce data races - Never use `time.Sleep` in tests; use channels or `t.Cleanup` --- ## Development commands ```bash make init # first-time setup: fetch deps, install tools, git hooks make build # compile to ./bin/app make run # go run with default flags make test # run all tests make test-race # tests + race detector make lint # go vet + golangci-lint make lint-fix # go fix + golangci-lint auto-fix make security # gosec + govulncheck make clean # remove bin/ ``` VSCode: `Ctrl+Shift+B` = build, `Ctrl+Shift+T` = test. Debug: use launch config "Debug: app" (F5). --- ## Adding new features (checklist) 1. Define the interface in `internal//` 2. Write the implementation and its unit tests (using a manual fake for the interface) 3. Wire it in `cmd/app/main.go` 4. Run `make lint test` before committing --- ## Known pitfalls - `govulncheck` makes network calls; excluded from pre-push hook (run manually) - `tools.go` has `//go:build tools` — it won't compile into the binary - `go fix` rewrites source files; run `make lint-fix` before committing after a Go version bump --- ## Recent work - 2026-03-29 — Stripped to stdlib-only: removed testify/mockery→manual fakes, zap→slog, viper→flag. - 2026-03-29 — Pre-commit hook moved to pre-push; go vet + go fix added to lint pipeline. - 2026-03-29 — Fixed stale docs and .golangci.yml local-prefixes; .vscode launch configs use CLI flags.