---
name: git-workflow-and-versioning
description: Git discipline for every code change: atomic commits, short-lived branches, trunk-based flow, semver, tags, and changelogs. Use when committing, branching, resolving conflicts, organizing parallel work, cutting a release, or choosing a version bump. Not for the end-of-branch merge/PR/cleanup decision: use finishing-a-development-branch. Not for UI implementation itself: use frontend-ui-engineering.
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: rewritten from patterns in addyosmani/agent-skills (MIT)
  category: Methodology & Process
---

# Git Workflow and Versioning

Commits are save points, branches are sandboxes, history is documentation. With agents generating code fast, commit discipline is what keeps changes reviewable and reversible.

## Core rules

1. **Trunk-based by default.** Main stays deployable. Feature branches merge back within 1-3 days. Every day a branch lives it accumulates merge risk. Incomplete features go behind flags, not on long-lived branches. Teams on gitflow keep the commit rules below regardless of branching model.
2. **Commit every green increment.** Pattern: implement slice, test, verify, commit, next slice. If the next change breaks, `git reset --hard HEAD` costs you one increment, not a day.
3. **Atomic commits.** One logical change per commit. "Add task feature, fix sidebar, update deps" is three commits pretending to be one.
4. **Messages explain why.** Format: `<type>: <short description>`, optional body for intent. Types: feat, fix, refactor, test, docs, chore. "update auth.ts" is not a message.
5. **Separate concerns.** Formatting apart from behavior. Refactors apart from features. Each is its own commit, ideally its own PR.
6. **Size the change.** ~100 lines is easy to review, ~300 acceptable for one logical change, ~1000 must be split before submitting.
7. **Branch names carry type:** `feature/task-creation`, `fix/duplicate-tasks`, `chore/update-deps`, `refactor/auth-module`. Delete branches after merge.

## Pre-commit pass

Before every commit: `git diff --staged` and read it, grep the staged diff for `password|secret|api_key|token`, run tests, lint, and type check. Automate with hooks (lint-staged + husky or equivalent). `.gitignore` covers `node_modules/`, `dist/`, `.env*`, `*.pem` from day one.

## Change summaries

After any modification, report three sections: CHANGES MADE (file by file), THINGS I DIDN'T TOUCH intentionally (adjacent problems left alone, with why), POTENTIAL CONCERNS (strictness choices, new dependencies, open questions). The "didn't touch" section proves scope discipline and catches wrong assumptions early.

## Parallel work and debugging

Worktrees give each branch its own directory: `git worktree add ../project-feature-a feature/task-creation`. Agents work in parallel without branch switching, failed experiments get deleted without loss. For hunting regressions: `git bisect`, `git log --grep`, `git blame`.

## Releases and versioning

The moment anything depends on your code, "latest on main" stops answering "what am I running and is it safe to upgrade". Version `MAJOR.MINOR.PATCH`: major for breaking, minor for backward-compatible additions, patch for backward-compatible fixes. Unsure whether a change breaks consumers: assume it does. Tag every release (`git tag -a v1.4.0 -m "Release 1.4.0"`), derive the version from the tag, keep a human-curated changelog written with the change, not reconstructed at release time. Read references/release-versioning.md when cutting a release, choosing a bump, or writing the changelog.

## Good vs bad

**Bad:** `git log --oneline` shows `x1y2z3a Add task feature, fix sidebar, update deps, refactor utils`. Reverting the sidebar fix means surgery. The "patch" release inside it renamed a response field consumers parsed.

**Good:** Four commits: the validation endpoint, the form component, the wiring, the tests. Each revertable alone. The field rename ships separately as a major with a deprecation window and a changelog entry.

## Verification

Before pushing, run `git log --oneline -10` and the staged-diff secret grep. Expect: each commit message names one logical change with a type prefix, and the grep returns nothing. A mixed commit: split it with `git reset` or interactive tooling before pushing. A secret: remove it and rotate the credential, history rewrites do not un-leak keys.

## Completion checklist

- [ ] Each commit does one logical thing
- [ ] Messages typed and explain why
- [ ] Tests, lint, type check pass before commit
- [ ] No secrets in any staged diff
- [ ] Formatting separated from behavior changes
- [ ] Branch short-lived, deleted after merge
- [ ] Change summary delivered with didn't-touch section
- [ ] Release (if any): correct bump, tag pushed, changelog entry curated

Any box unchecked: not done. Fix or say so.

## Red flags

Verbatim rationalizations that precede unreviewable history:

- "I'll commit when the feature is done"
- "The message doesn't matter, I'll squash later"
- "It's just a small fix, bump the patch"
- "The changelog is just the commit log"
- "I don't need a .gitignore yet"

Closure rule: if a change is not in a typed, atomic, secret-free commit, it does not count as done, regardless of what is sitting in the working tree.

## Footguns

- **Giant uncommitted working tree.** One agent misstep destroys hours. Fix: commit every green slice, no exceptions.
- **Breaking change in a patch.** Consumers observe behavior, not diff size (Hyrum's Law). Fix: judge the bump by what consumers can observe, assume breaking when unsure.
- **Force-pushing shared branches.** Everyone else's history breaks. Fix: force-push only your own unshared branches, and only when explicitly requested.
- **Version hand-edited out of sync with the tag.** Artifact says 1.4.0, tag says 1.3.2, nobody can reproduce the build. Fix: derive the version from the tag in CI.
