Add the canonical workflow reference (docs/WORKFLOW.md): branch model (main/develop + feature/fix/hotfix), Conventional Commits, SemVer, tagging, and the three-environment strategy (native dev, Docker staging, Linux prod). Seed VERSION (0.1.0) as the single source of truth and a Keep-a-Changelog CHANGELOG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.5 KiB
InboxIntel — Git Workflow & Environment Strategy
The single source of truth for how we branch, commit, version, and promote code across environments. Optimised for a solo developer on Windows 11 with a future Linux production server. Kept deliberately lightweight — every rule here earns its place.
1. Branch strategy
A trimmed GitHub Flow + a long-lived develop model. Two permanent branches,
short-lived branches off develop.
| Branch | Lives for | Purpose | Deploys to |
|---|---|---|---|
main |
forever | Always releasable. Every commit is tagged & shippable | production |
develop |
forever | Integration branch. What staging runs | staging |
feature/* |
hours–days | One feature or refactor | dev (local) |
fix/* |
hours | Non-urgent bug fix | dev (local) |
hotfix/* |
minutes–hrs | Urgent prod fix, branched from main |
prod (fast) |
release/x.y.0 |
optional | Only if a release needs stabilisation before tagging | staging |
Why this shape (not full GitFlow): a solo dev doesn't need GitFlow's ceremony
(separate release managers, parallel release trains). But keeping develop
separate from main gives one thing that pure trunk-based can't: a staging
environment that always mirrors develop while main stays clean and
tag-perfect for production. release/* exists only when you want to freeze
features and stabilise — skip it for routine work.
Normal flow
main ──────●────────────────────●────────── (tagged: v1.2.0, v1.3.0)
\ /
develop ──●──●──●──●──●──●──●──●──────────── (staging)
\ / \ /
feature/x ●─● \ /
fix/y ●───●
git switch develop && git pullgit switch -c feature/sender-policy- Commit in small conventional commits.
- Push, open a PR into
develop(Gitea). CI must be green. - Squash-merge. Delete the branch.
- When
developis ready to ship → PRdevelop → main, tag, deploy.
2. Commit conventions — Conventional Commits
Format: type(scope): short imperative summary
Types: feat, fix, docs, refactor, test, chore, ci, perf,
build, style, security.
Rules:
- Summary ≤ 72 chars, imperative mood ("add", not "added").
- One logical change per commit.
- Body explains why, not what (the diff shows what).
- Breaking change → add
!(feat!:) and aBREAKING CHANGE:footer.
Examples (matching this repo's history):
feat(ui): F1+F2 — component primitives + rebuilt app shell
fix(security): systemic IDOR safeguard via EF global query filters
ci: add Gitea Actions build + test pipeline
Why: conventional commits drive automatic semver bumps and a generated
CHANGELOG, and make git log scannable. feat → minor, fix → patch,
BREAKING CHANGE → major.
3. Pull request / merge flow (Gitea)
Even solo, PRs are worth it: they run CI, give a diff review checkpoint, and build a paper trail.
- Target:
feature/*andfix/*→develop.develop/hotfix/*→main. - Gate: the
CIworkflow (backend build+test, frontend build) must pass. - Merge style: squash-merge feature branches (one clean commit on
develop). Merge commit fordevelop → main(preserves the integration history and makes the release boundary visible). - Branch protection (Gitea → Settings → Branches): protect
mainanddevelop; require status checks to pass; disallow force-push.
4. Versioning — Semantic Versioning (MAJOR.MINOR.PATCH)
- MAJOR — breaking API/behaviour change.
- MINOR — backward-compatible feature.
- PATCH — backward-compatible fix.
The current version lives in VERSION and is the one place bumped
per release. Pre-1.0 while scaffolding: stay on 0.x (minor = features, patch =
fixes; anything may change).
5. Tagging & releases
Tags are cut only on main, annotated, prefixed v:
git switch main && git pull
# bump VERSION + CHANGELOG in a release commit, then:
git tag -a v1.3.0 -m "v1.3.0 — sender policy + staging overlay"
git push origin main --tags
- Tag == the exact commit deployed to production == the rollback target.
- The tag message summarises the release; details live in
CHANGELOG.md.
6. Hotfix process
For a bug already in production:
git switch main && git pull
git switch -c hotfix/oauth-callback-500
# fix + test
git commit -m "fix(security): guard null OAuth state on callback"
# PR hotfix/* -> main, CI green, merge
git switch main && git pull
git tag -a v1.3.1 -m "v1.3.1 hotfix — OAuth callback" && git push origin main --tags
# deploy the tag, THEN back-merge so develop doesn't lose the fix:
git switch develop && git merge main && git push
The back-merge to develop is the step people forget — without it the next
release silently reintroduces the bug.
7. Environments
| Env | Host | Runtime | Config source | Purpose |
|---|---|---|---|---|
| Development | Windows 11 (native) | dotnet run + vite (hot reload) |
appsettings.Development.json + user-secrets |
fast iteration, debugging |
| Staging | Windows 11 (Docker) | Linux containers, ASPNETCORE_ENVIRONMENT=Production |
deploy/.env.staging + docker-compose.staging.yml |
production rehearsal |
| Production | Linux server (Docker) | identical Linux containers | deploy/.env on the server |
live |
Parity principle: dev is fast (native, Windows) and not production-shaped —
that's fine, it's for inner-loop speed. Staging is the parity gate: it runs the
same Linux images Docker builds for production, so "works in staging" genuinely
predicts "works in prod". The only staging↔prod differences are the dev banner,
capped sync, ports, and volume namespace — see docker-compose.staging.yml.
Commands
# DEV (native, hot reload) — two terminals
dotnet run --project src/InboxIntel.Api # http://localhost:5080
cd frontend; npm run dev # http://localhost:5173
# STAGING (production-shaped, on Windows via Docker)
./deploy/up.ps1 -Staging # http://localhost:18081
./deploy/down.ps1 -Staging
# PRODUCTION-shaped run locally (smoke test the real config)
./deploy/up.ps1 # http://localhost:8081
Dev and staging use different ports and different volumes, so they can run at the same time and never share a database.
8. Git hooks
Installed via core.hooksPath (run once per clone):
./scripts/install-hooks.ps1
- pre-commit (fast): blocks committed
.env/secrets, verifiesdotnet format. - pre-push (thorough):
dotnet build -c Release+dotnet test+ frontend build — the same gate CI runs, caught before the push.
Emergency bypass: --no-verify. CI still enforces the gate server-side, so a
bypassed push can still be rejected by branch protection.