Stage 7 · Master
Phase 1 — Project Foundation
Monorepo Setup
Turn an empty directory into the tracked repository shape the architecture ADR promised, before a single go.mod exists.
From Empty Directory to a Tracked Repository
This lesson has no Go code, because there is nothing to compile yet — only a shape to create and commit. The goal is a repository whose top level makes the ADR from the previous lesson legible at a glance: anyone who runs ls at the root should be able to guess where shared libraries live, where deployable services live, and where operational scripts live, without opening a single file.
What Each Top-Level Directory Is (and Is Not) For
- pkg/ — shared Go modules imported by services/, never the other way around. Each subdirectory (pkg/config, pkg/logging, pkg/apperr) will get its own go.mod in later lessons.
- services/ — one directory per deployable binary. services/organization is the only one this course builds; its Dockerfile and Kubernetes manifests live inside it, not in a separate top-level deploy/ tree.
- deploy/ — assets that describe running the whole platform locally, not a single service: docker-compose.yml lives at the repository root instead, so deploy/ stays reserved for cluster-wide or environment-wide assets added in later phases.
- scripts/ — small operational shell scripts (git hooks, one-off maintenance) that are not part of any Go module and should never be imported by Go code.
- docs/ — ADRs, schema notes, and anything else that explains a decision rather than implementing it.
Go's internal/ visibility rule (covered in the Folder Structure lesson) will make some boundaries compiler-enforced. The pkg/ → services/ direction is not one of them — nothing stops a shared package from importing a service package by mistake. It is a code-review rule the team agreed to in ADR 0001, and it is worth restating here because nothing will fail loudly if it is violated.
Ignoring Build Artifacts Without Ignoring Intent
A .gitignore written before the first go.mod exists should still be precise: it should exclude compiled binaries, local environment files, and editor noise, without excluding anything that documents intent — like an empty directory's purpose. Because git does not track empty directories, pkg/, services/, deploy/, and scripts/ will disappear from a fresh clone until the first real file lands inside each. That is expected and is not worked around with .gitkeep files in this repository — the first lesson that adds a module to each directory also adds its first real file.
# Compiled binaries from any module's cmd/*/main.go
/services/*/bin/
*.exe
*.out
# Local environment overrides — never committed, see the Environment Variables lesson
.env
.env.*
!.env.example
# Go tooling
go.work.sum.bak
# Editor and OS noise
.vscode/
.idea/
.DS_Store
# hoa-platform
A monorepo of independently versioned Go modules that together make up an
HOA (Homeowners' Association) management platform.
## Layout
- `pkg/` — shared libraries (config loading, logging, semantic errors).
Imported by `services/`, never the reverse.
- `services/` — one deployable binary per service. Each has its own
`go.mod`, `Dockerfile`, and Kubernetes manifests.
- `docs/` — architecture decision records and design notes.
- `scripts/` — operational scripts (git hooks, maintenance tasks).
## Workspace
This repository has no root `go.mod`. Modules are composed locally with
`go.work` (see `docs/adr/0001-monorepo-and-layered-services.md`). Run
Go commands from inside a specific module, or use the root `Makefile`
once it exists, rather than from the repository root.
What Belongs in the First Commit
The first commit should contain exactly the ADR, the .gitignore, and the README — nothing generated, nothing half-finished. This makes git log --oneline readable from the very start: one commit per lesson's milestone, which is the convention this course follows for the rest of Phase 1 and Phase 2.
Verifying the Shape Before Moving On
Two quick checks confirm the repository is in the state this lesson describes: git log should show exactly one commit, and git status should report a clean working tree. If either check fails — an extra untracked file, or a second accidental commit — resolve it now, because every later lesson assumes this starting point.
Applied exercise
Document the scripts directory before it has any scripts
scripts/ is currently empty and, per this lesson, invisible to a fresh clone. Future lessons (Docker, Makefile, Git Workflow) will each add a script there.
- Create scripts/README.md explaining that this directory holds operational shell scripts only, never Go source.
- List the two scripts this course will add later by name, even though they do not exist yet: a git pre-commit hook and a database wait-for-ready helper.
- Commit the file with a Conventional-Commits-style message using the docs: prefix.
Deliverable
A committed scripts/README.md and a second entry in git log --oneline.
Completion checks
- scripts/README.md exists and is tracked (git ls-files scripts/README.md prints the path).
- The commit message starts with docs:.
- git log --oneline now shows two commits total.
Why does deploy/ stay reserved rather than holding docker-compose.yml for local development?
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.