Stage 1 · Code
Capstone — Ship a Real Tool
Project Layout
Organise cmd, internal packages, and config files so the project grows without becoming a mess.
Standard Go Layout
There is no single enforced layout in Go, but the community has converged on conventions. Understanding them helps you read any open-source Go project.
| Directory | Purpose |
|---|---|
cmd/appname/ | Main packages — one per deployable binary |
internal/ | Private packages — not importable by other modules |
pkg/ | Public library packages (rare in small tools) |
testdata/ | Test fixtures, golden files, sample inputs |
scripts/ | Build helpers, release scripts, CI snippets |
tasker/
├── cmd/
│ └── tasker/
│ └── main.go ← entry point, flag parsing, subcommand dispatch
├── internal/
│ ├── task/
│ │ ├── task.go ← Task type, TaskStore type, constructors
│ │ ├── store.go ← load/save JSON with atomic write
│ │ └── store_test.go ← round-trip tests with t.TempDir()
│ └── cli/
│ ├── commands.go ← Add, List, Done, Delete handler funcs
│ └── commands_test.go ← integration tests with a real MemStore
├── testdata/
│ └── sample_tasks.json ← fixture used by integration tests
├── go.mod
├── go.sum
└── Makefileinternal Packages in Practice
Makefile and Scripts
A Makefile with idiomatic targets makes it easy for anyone to build, test, and release without memorizing flags. Document each target.
# Build the binary
make build
# Run tests with race detection and coverage
make test
# Format and vet
make lint
# Build release binaries for all platforms
make releasecmd/tasker/main.go should do exactly three things: parse the subcommand from os.Args, build the dependencies (store, logger), and call the appropriate function from internal/cli. All business logic belongs in internal/. A thin main.go is easy to test because you can construct the same dependency graph in tests without running the binary.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.