| Category | Tool |
|---|---|
| CLI | Cobra + Fang v2 (styled help, manpages, completions) |
| Config | Viper (YAML + env vars + defaults) |
| DI | samber/do v2 (lazy dependency injection) |
| Functional | samber/lo (map, filter, reduce) |
| Monads | samber/mo (Option, Result, Either) |
| Errors | samber/oops (structured errors with context) |
| Logging | zerolog + slog-zerolog bridge |
| Testing | testify (assert + require) |
| Linting | golangci-lint v2 (50+ linters, strict config) |
| Tasks | Task (build, test, lint, ci) |
| Tools | mise (Go, Task, golangci-lint, lefthook versions) |
| Hooks | Lefthook (pre-commit, pre-push, conventional commits) |
| Release | GoReleaser v2 (cross-compile, checksums, changelog) |
| CI/CD | GitHub Actions (lint + test + build matrix + release) |
| Deps | Renovate (automated dependency updates) |
| AI Skills | cc-skills-golang (opinionated agentic coding skills in .agents/) |
| Init | huh + lipgloss (interactive project setup via task init) |
Click "Use this template" on GitHub, then:
git clone git@github.com:yourname/yourproject.git
cd yourprojectRun the interactive init task to rename module, binary, and env prefix:
mise install # Install Go, Task, golangci-lint, lefthook
task init # Rename + deps + git hooks
task ci # Verify everything workstask init uses huh to prompt for your module path, binary name, env prefix, and which AI coding assistant harnesses to enable (.adal, .augment, .claude, etc.). For each selected harness, it creates symlinks from .<harness>/skills/ into .agents/skills/. After setup, it rewrites all files, renames cmd/og-template/, runs go mod tidy, downloads deps, installs git hooks, and cleans up after itself (removes cmd/init/ and the init task from Taskfile.yml).
.
βββ .agents/skills/ # AI coding skills (cc-skills-golang, symlinked per harness)
βββ cmd/og-template/ # CLI entrypoint and commands
β βββ main.go # fang.Execute with signal handling
β βββ root.go # Root cobra command
β βββ config.go # config show/validate commands
β βββ version.go # version command
βββ internal/
β βββ config/ # Viper config loading + validation
β β βββ config.go # Config struct + Validate()
β β βββ loader.go # Load() returns mo.Result[*Config]
β βββ di/ # samber/do dependency injection
β β βββ container.go # Root container with oops errors
β β βββ register.go # Service registration
β β βββ config_service.go
β β βββ logger_service.go # zerolog + slog bridge
β βββ vinfo/ # Build version metadata (ldflags)
βββ .github/workflows/
β βββ ci.yml # Lint + test + cross-platform build
β βββ release.yml # GoReleaser on tag push
βββ Taskfile.yml # build, test, lint, fmt, ci, clean, init
βββ .golangci.yml # 50+ linters, strict settings
βββ .goreleaser.yaml # Cross-compile + changelog + archives
βββ .mise.toml # Pinned tool versions
βββ lefthook.yml # Pre-commit, pre-push, conventional commits
βββ config.example.yaml # Example configuration
task # List all tasks
task init # Rename + deps + hooks (first-time only)
task build # Build binary with ldflags
task run # Build and run
task test # Run tests with race detector
task test-short # Run short tests only
task test-coverage # Tests + coverage HTML report
task lint # golangci-lint
task fmt # golangci-lint --fix
task ci # fmt + lint + test + build
task deps # Download dependencies
task tidy # go mod tidy
task clean # Remove all artifacts and cachesThe CLI uses Cobra for command structure and Fang v2 as a wrapper that adds styled help pages, automatic --version flag, manpage generation via a hidden man subcommand, and shell completions out of the box.
Entry point (cmd/og-template/main.go):
- Sets up signal handling (
SIGINT,SIGTERM) - Calls
fang.Execute()which wraps the root Cobra command - Returns exit code 1 on error, 0 on success
Configuration loads from multiple sources with this precedence:
--configCLI flag (explicit path)- Environment variables prefixed with
OGTEMPLATE_(e.g.OGTEMPLATE_APP_NAME,OGTEMPLATE_LOGGING_LEVEL) config.yamlin the current directory$HOME/.config/og-template/config.yaml- Built-in defaults (development mode, info logging, pretty format)
The loader returns mo.Result[*Config] for monadic error handling. Config is validated after loading β invalid values produce clear error messages.
og-template config show # Display all resolved config values
og-template config validate # Check config is validServices are registered lazily in internal/di/register.go and resolved on first use. The container pattern:
NewContainer(configPath)creates the root injectorRegisterServices()registers all providers (config, logger, etc.)- Services resolve dependencies via
do.MustInvoke[T](injector)in their constructors ShutdownWithContext()tears down all services gracefully
To add a new service: create it in internal/yourpkg/, register with do.Provide(injector, NewYourService) in register.go.
Errors use samber/oops for structured context throughout the DI and config layers:
return nil, oops.
In("config"). // domain
Code("invalid_config"). // machine-readable code
Wrapf(err, "load configuration")This gives you stack traces, domain context, and error codes without losing the original error chain.
The logger service creates a zerolog.Logger and bridges it to Go's slog via samber/slog-zerolog. This means:
- Use
slogin application code (stdlib, portable) - Get zerolog's performance and structured output under the hood
- Pretty console output in development, JSON in production
- Controlled via
logging.level(debug/info/warn/error) andlogging.format(pretty/json)
- lo: Generic slice/map operations β
lo.Map,lo.Filter,lo.SliceToMap,lo.MaxBy,lo.Uniq, etc. - mo: Monadic types β
mo.Option[T],mo.Result[T],mo.Either[L, R]. Config loading returnsmo.Resultfor composable error handling.
Three hooks are installed via lefthook install:
pre-commit (runs in parallel):
golangci-lint run --fixon staged.gofiles (auto-stages fixes)task test-shortfor fast feedback
pre-push:
task test(full test suite with race detector)task build(ensures the binary compiles)
commit-msg:
- Enforces Conventional Commits format
- Valid types:
feat,fix,docs,style,refactor,perf,test,chore,ci,build - Format:
type(scope?): subject - Examples:
feat: add user auth,fix(config): handle missing file,chore: bump deps
Hooks skip on merge and rebase to avoid friction.
The .golangci.yml enables 50+ linters with strict settings and no exclusions on test files:
| Setting | Value | Why |
|---|---|---|
gocyclo.min-complexity |
10 | Keep functions simple |
gocognit.min-complexity |
15 | Enforce readability |
funlen.lines |
80 | Short functions |
funlen.statements |
50 | Short functions |
lll.line-length |
120 | Reasonable line width |
dupl.threshold |
100 | Catch copy-paste |
errcheck.check-type-assertions |
true | No unchecked type casts |
errcheck.check-blank |
true | No _ = err |
exhaustruct |
project packages only | Catch missing struct fields |
Only protobuf (.pb.go) and generated (_generated.go) files are excluded.
task build injects version metadata via -ldflags:
Versionβ fromgit describe --tags --always --dirtyCommitβ fromgit rev-parse --short HEADBuildDateβ UTC timestamp
The internal/vinfo package exposes String() which formats these for --version output. Falls back to debug.ReadBuildInfo() for go install builds.
CI (.github/workflows/ci.yml):
- Triggers on push/PR to main/master
- Runs golangci-lint via official action
- Runs tests with coverage (uploads to Codecov)
- Cross-compiles build matrix: linux/darwin/windows Γ amd64/arm64
- Uses
go-version-file: go.mod(always matches local Go version) - Concurrency groups cancel superseded runs
Release (.github/workflows/release.yml):
- Triggers on
v*.*.*tag push - Runs GoReleaser v2 to build, archive, generate changelog, and publish GitHub release
goreleaser builds for linux/darwin/windows (amd64 + arm64), creates tar.gz archives (zip for Windows), generates SHA-256 checksums, and publishes a GitHub release with a conventional-commit-based changelog grouped by type (features, fixes, performance).
git tag v0.1.0
git push origin v0.1.0 # Triggers release workflowAll tool versions are pinned in .mise.toml:
- Go β pinned to specific patch version
- Task β pinned
- golangci-lint β pinned
- lefthook β pinned
Run mise install to get the exact versions. No global installs needed.
Build caches are kept per-project (not in $HOME) for isolation:
.gocache/β Go build cache.gomodcache/β Go module cache.tmp/β golangci-lint cache, temp files
All are gitignored. task clean removes everything.
Renovate is pre-configured with config:recommended. Once enabled on your GitHub repo, it will automatically open PRs for dependency updates in go.mod.
The .agents/skills/ directory contains cc-skills-golang β a curated set of agentic coding skills for AI assistants working in Go codebases. These provide opinionated guidance for code generation, testing patterns, and project conventions.
.agents/skills/ is the single source of truth. At task init, you pick which AI coding assistants you use and the init tool creates .<harness>/skills/ symlink directories pointing into .agents/skills/. Supported harnesses include .adal, .augment, .claude, .codebuddy, .continue, .cortex, .crush, .factory, .goose, .iflow, .junie, .kilocode, .kiro, .kode, .openhands, .qoder, .qwen, .roo, .trae, .windsurf, .zencoder, and more.
MIT
