template/README.md
djmil 4301f11efd docs: update README and CLAUDE.md for v0.4.0
README: slim to landing-page essentials; add "Design forces" section
explaining why pkg/result and pkg/logger exist (form over mechanism).
Remove stale API docs — those live in package comments.

CLAUDE.md: add "form over mechanism" design principle; update logging
rule to document NewCLI two-mode model; add v0.4.0 to recent work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 17:04:30 +00:00

158 lines
5.8 KiB
Markdown

# template
A Go project template for **PoC, hobby projects, and small publishable packages**.
Clone it, rename the module, run `make init`, and you're coding.
Demonstrates idiomatic Go patterns: stdlib-only dependencies, consumer-defined interfaces,
manual fakes for testing, and a clean `go.mod` that stays free of dev tool noise —
so packages extracted from this template can be published without module graph pollution.
---
## Design forces
Two libraries in this template emerged from a recurring friction in Go:
**mechanism leaking into intent**.
**`pkg/result`** exists because `if err != nil { return nil, err }` repeated on every line
obscures what the code is actually doing. The happy path drowns in error-routing boilerplate.
`result.Expect` moves the exit logic to the edges — constructors and entry points — so the
body of a function reads as intent, not plumbing.
**`pkg/logger`** exists because `fmt.Fprintln(os.Stderr, "error: "+msg)` leaks *how* (write
to stderr, format a string) into code that should only express *what* (something went wrong).
The same friction appeared in format selection: choosing between human text and JSON based on
the execution environment is mechanism, not business logic. `logger.NewCLI` hides that choice —
the call site just says `log.Warn(...)` and gets the right output for the context.
The pattern: **constructors own the mechanism; call sites own the intent.**
---
## Features
| Area | Tool | Purpose |
|---|---|---|
| Language | Go 1.25 | Modules, toolchain directive |
| Logging | standard `log/slog` | Auto-detects terminal vs pipe: human text or JSON; optional debug file dump |
| Config | standard `flag` | CLI flags with defaults, no config files |
| Linting | [golangci-lint](https://golangci-lint.run) | Aggregated linters, one config file |
| Security | [gosec](https://github.com/securego/gosec) + [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) | SAST + dependency vulnerability scan |
| Tests | standard `testing` | Table-driven tests, manual fakes, no third-party test framework |
| Git hooks | custom pre-push | gofmt + go vet + golangci-lint + gosec on every push |
| Tool versions | `tools.versions` + `go run` | Pinned versions without polluting `go.mod` |
| Dev environment | devcontainer | Reproducible VSCode / GitHub Codespaces setup |
| IDE | VSCode | Launch configs, tasks, recommended settings |
---
## Getting started
### Prerequisites
- Go 1.25+
- Git
- (Optional) [VSCode](https://code.visualstudio.com/) + [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go)
- (Optional) Docker for devcontainer
### 1. Clone and rename
```bash
git clone https://gitea.djmil.dev/go/template my-project
cd my-project
# Interactive rename — updates module path, config, devcontainer, and docs:
./rename.sh
```
### 2. Init (run once)
```bash
make init # fetches deps, configures git hooks
make tools # (optional) install tool binaries to GOPATH/bin for IDE integration
```
### 3. Build and run
```bash
make build # compiles to ./bin/app
make run # go run with default flags
```
---
## Daily workflow
```bash
make test # run all tests
make test-race # … with race detector
make lint # go vet + golangci-lint
make lint-fix # go fix + golangci-lint auto-fix
make security # gosec + govulncheck
make release # list tags, or run full checks then tag+push (make release VERSION=v0.1.0)
```
> **Keyboard shortcut (VSCode):** `Ctrl+Shift+B` → build, `Ctrl+Shift+T` → test.
### Claude Code commands
If you use [Claude Code](https://claude.ai/code), the repo ships a custom slash command:
| Command | What it does |
|---|---|
| `/new-package <name>` | Scaffolds `internal/<name>/` with a concrete type, constructor, doc comments, and a black-box test file — all wired to project conventions |
Commands live in `.claude/commands/` and are available to anyone who clones the repo.
---
## Project structure
```
.
├── cmd/
│ └── app/
│ ├── config.go # Flag parsing and Config struct (all flags live here)
│ └── main.go # Composition root (thin — just wiring)
├── internal/
│ └── greeter/ # Example domain package (replace with yours)
├── pkg/ # Publishable packages — no app-specific assumptions
│ ├── logger/ # slog wrapper: NewCLI, New, NewWriter, WithField/WithFields
│ ├── result/ # Happy-path error handling (Expect[T])
│ └── testutil/ # Test helpers (ResultOk, Equal, …)
├── .devcontainer/ # VSCode / Codespaces container definition
├── .githooks/ # pre-push hook (gofmt + vet + lint + gosec)
├── .vscode/ # Launch configs, tasks, editor settings
├── tools.versions # Pinned tool versions (Makefile + hook source this)
├── .golangci.yml # Linter configuration
└── Makefile # All development commands
```
---
## Releasing
```bash
make release # list all existing tags
make release VERSION=v0.1.0 # run full checks, then tag and push
```
---
## Devcontainer
Open this repo in VSCode and choose **"Reopen in Container"**. Run `make init` to
configure git hooks, then `make tools` for IDE-integrated tool binaries.
Works with GitHub Codespaces out of the box.
---
## Next steps (not included)
- **HTTP server** — add [chi](https://github.com/go-chi/chi) or [gin](https://github.com/gin-gonic/gin)
- **Database** — add [sqlx](https://github.com/jmoiron/sqlx) or [ent](https://entgo.io/)
- **CI** — add a Gitea Actions / CI pipeline running `make lint test security`
- **Docker** — add a multi-stage `Dockerfile` to publish the binary as a container image
- **OpenTelemetry** — add tracing with `go.opentelemetry.io/otel`