Git workflow and versioning

Structures git workflow practices.

How to use it

Claude Code
  1. Run the line below. It pulls the whole folder into ~/.claude/skills/git-workflow-and-versioning, including the files SKILL.md points to.
  2. Describe your job in plain words. Claude Code follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit addyosmani/agent-skills/skills/git-workflow-and-versioning#main ~/.claude/skills/git-workflow-and-versioning

For one project only, change the path to .claude/skills/git-workflow-and-versioning. This skill also uses auth.ts, package.json, package-lock.json — copying SKILL.md alone won't be enough. See the folder on GitHub.

Claude (web or desktop app)
  1. On this page open ⋯ → Download .md.
  2. Save it as SKILL.md in a folder, zip the folder, then Customize → Skills → + → Create skill → Upload a skill.
  3. Pick the file and Save. Claude shows the name and description and runs a security scan.
  4. Check the skill is switched on.
  5. Start a new chat and describe your job in plain words. The AI follows the skill from there.
ChatGPT or another app
  1. ChatGPT: make a Project and paste it into Instructions.
  2. Neither? Paste it at the top of a new chat — it works for that chat.
Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Source of Git workflow and versioning

Show the full text356 lines
namedescription
git-workflow-and-versioningStructures git workflow practices. Use when making any code change. Use when committing, branching, resolving conflicts, splitting uncommitted work in a messy working tree into clean atomic commits, opening or reviewing a pull request (PR), pushing to a remote, or when you need to organize work across multiple parallel streams. Use when cutting a release, choosing a semantic version bump, tagging, or writing a changelog.

Git Workflow and Versioning

Overview

Git is your safety net. Treat commits as save points, branches as sandboxes, and history as documentation. With AI agents generating code at high speed, disciplined version control is the mechanism that keeps changes manageable, reviewable, and reversible.

When to Use

Always. Every code change flows through git.

Core Principles

Keep main always deployable. Work in short-lived feature branches that merge back within 1-3 days. Long-lived development branches are hidden costs — they diverge, create merge conflicts, and delay integration. DORA research consistently shows trunk-based development correlates with high-performing engineering teams.

main ──●──●──●──●──●──●──●──●──●──  (always deployable)
        ╲      ╱  ╲    ╱
         ●──●─╱    ●──╱    ← short-lived feature branches (1-3 days)

This is the recommended default. Teams using gitflow or long-lived branches can adapt the principles (atomic commits, small changes, descriptive messages) to their branching model — the commit discipline matters more than the specific branching strategy.

  • Dev branches are costs. Every day a branch lives, it accumulates merge risk.
  • Release branches are acceptable. When you need to stabilize a release while main moves forward.
  • Feature flags > long branches. Prefer deploying incomplete work behind flags rather than keeping it on a branch for weeks.
1. Commit Early, Commit Often

Each successful increment gets its own commit. Don't accumulate large uncommitted changes.

Work pattern:
  Implement slice → Test → Verify → Commit → Next slice

Not this:
  Implement everything → Hope it works → Giant commit

Commits are save points. If the next change breaks something, you can revert to the last known-good state instantly.

2. Atomic Commits

Each commit does one logical thing:

# Good: Each commit is self-contained
git log --oneline
a1b2c3d Add task creation endpoint with validation
d4e5f6g Add task creation form component
h7i8j9k Connect form to API and add loading state
m1n2o3p Add task creation tests (unit + integration)

# Bad: Everything mixed together
git log --oneline
x1y2z3a Add task feature, fix sidebar, update deps, refactor utils
3. Descriptive Messages

Commit messages explain the why, not just the what:

# Good: Explains intent
feat: add email validation to registration endpoint

Prevents invalid email formats from reaching the database.
Uses Zod schema validation at the route handler level,
consistent with existing validation patterns in auth.ts.

# Bad: Describes what's obvious from the diff
update auth.ts

Format:

<type>: <short description>

<optional body explaining why, not what>

Types:

  • feat — New feature
  • fix — Bug fix
  • refactor — Code change that neither fixes a bug nor adds a feature
  • test — Adding or updating tests
  • docs — Documentation only
  • chore — Tooling, dependencies, config
4. Keep Concerns Separate

Don't combine formatting changes with behavior changes. Don't combine refactors with features. Each type of change should be a separate commit — and ideally a separate PR:

# Good: Separate concerns
git commit -m "refactor: extract validation logic to shared utility"
git commit -m "feat: add phone number validation to registration"

# Bad: Mixed concerns
git commit -m "refactor validation and add phone number field"

Separate refactoring from feature work. A refactoring change and a feature change are two different changes — submit them separately. This makes each change easier to review, revert, and understand in history. Small cleanups (renaming a variable) can be included in a feature commit at reviewer discretion.

5. Size Your Changes

Target ~100 lines per commit/PR. Changes over ~1000 lines should be split. See the splitting strategies in code-review-and-quality for how to break down large changes.

~100 lines  → Easy to review, easy to revert
~300 lines  → Acceptable for a single logical change
~1000 lines → Split into smaller changes

Branching Strategy

Feature Branches
main (always deployable)
  │
  ├── feature/task-creation    ← One feature per branch
  ├── feature/user-settings    ← Parallel work
  └── fix/duplicate-tasks      ← Bug fixes
  • Branch from main (or the team's default branch)
  • Keep branches short-lived (merge within 1-3 days) — long-lived branches are hidden costs
  • Delete branches after merge
  • Prefer feature flags over long-lived branches for incomplete features
Branch Naming
feature/<short-description>   → feature/task-creation
fix/<short-description>       → fix/duplicate-tasks
chore/<short-description>     → chore/update-deps
refactor/<short-description>  → refactor/auth-module

Working with Worktrees

For parallel AI agent work, use git worktrees to run multiple branches simultaneously:

# Create a worktree for a feature branch
git worktree add ../project-feature-a feature/task-creation
git worktree add ../project-feature-b feature/user-settings

# Each worktree is a separate directory with its own branch
# Agents can work in parallel without interfering
ls ../
  project/              ← main branch
  project-feature-a/    ← task-creation branch
  project-feature-b/    ← user-settings branch

# When done, merge and clean up
git worktree remove ../project-feature-a

Benefits:

  • Multiple agents can work on different features simultaneously
  • No branch switching needed (each directory has its own branch)
  • If one experiment fails, delete the worktree — nothing is lost
  • Changes are isolated until explicitly merged

The Save Point Pattern

Agent starts work
    │
    ├── Makes a change
    │   ├── Test passes? → Commit → Continue
    │   └── Test fails? → Revert to last commit → Investigate
    │
    ├── Makes another change
    │   ├── Test passes? → Commit → Continue
    │   └── Test fails? → Revert to last commit → Investigate
    │
    └── Feature complete → All commits form a clean history

This pattern means you never lose more than one increment of work. If an agent goes off the rails, git reset --hard HEAD takes you back to the last successful state.

Change Summaries

After any modification, provide a structured summary. This makes review easier, documents scope discipline, and surfaces unintended changes:

CHANGES MADE:
- src/routes/tasks.ts: Added validation middleware to POST endpoint
- src/lib/validation.ts: Added TaskCreateSchema using Zod

THINGS I DIDN'T TOUCH (intentionally):
- src/routes/auth.ts: Has similar validation gap but out of scope
- src/middleware/error.ts: Error format could be improved (separate task)

POTENTIAL CONCERNS:
- The Zod schema is strict — rejects extra fields. Confirm this is desired.
- Added zod as a dependency (72KB gzipped) — already in package.json

This pattern catches wrong assumptions early and gives reviewers a clear map of the change. The "DIDN'T TOUCH" section is especially important — it shows you exercised scope discipline and didn't go on an unsolicited renovation.

Pre-Commit Hygiene

Before every commit:

# 1. Check what you're about to commit
git diff --staged

# 2. Ensure no secrets
git diff --staged | grep -i "password\|secret\|api_key\|token"

# 3. Run tests
npm test

# 4. Run linting
npm run lint

# 5. Run type checking
npx tsc --noEmit

Automate this with git hooks:

// package.json (using lint-staged + husky)
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

Handling Generated Files

  • Commit generated files only if the project expects them (e.g., package-lock.json, Prisma migrations)
  • Don't commit build output (dist/, .next/), environment files (.env), or IDE config (.vscode/settings.json unless shared)
  • Have a .gitignore that covers: node_modules/, dist/, .env, .env.local, *.pem

Using Git for Debugging

# Find which commit introduced a bug
git bisect start
git bisect bad HEAD
git bisect good <known-good-commit>
# Git checkouts midpoints; run your test at each to narrow down

# View what changed recently
git log --oneline -20
git diff HEAD~5..HEAD -- src/

# Find who last changed a specific line
git blame src/services/task.ts

# Search commit messages for a keyword
git log --grep="validation" --oneline

Release & Versioning

Commits are how you track change; a version is how your consumers track it. The moment anything else depends on your code — another team, a published package, a deployed client — "latest on main" stops being a sufficient answer to "what am I running, and is it safe to upgrade?" A version number and a changelog are the contract that answers it.

Semantic Versioning

For anything with consumers, version MAJOR.MINOR.PATCH and let the number carry meaning:

  MAJOR  breaking change — consumers must change their code to upgrade
  MINOR  new functionality, backward-compatible — safe to upgrade
  PATCH  bug fix, backward-compatible — safe to upgrade

The number is a promise, so make the code match it. A "patch" that changes behavior consumers relied on is a major change wearing a disguise (Hyrum's Law — see the api-and-interface-design skill). When unsure whether a change is breaking, assume it is; a surprise major is far cheaper than a broken consumer.

Tag the release, and let the tag be the source of truth

A release is an immutable point in history, not a moving branch. Tag it so it can always be reproduced:

git tag -a v1.4.0 -m "Release 1.4.0"
git push origin v1.4.0

Derive the version from the tag rather than hand-editing it in scattered files, so the artifact, the tag, and the changelog can never disagree.

Keep a changelog written for humans

A changelog is not git log. It's the curated, consumer-facing answer to "what changed and do I care?" — grouped by Added / Changed / Fixed / Deprecated / Removed / Security, newest on top, every entry phrased around user impact, not internal mechanics.

## [1.4.0] - 2025-06-12
### Added
- Bulk task import via CSV
### Fixed
- Timezone drift in recurring task due dates
### Deprecated
- `GET /v1/tasks/all` — use the paginated `GET /v1/tasks` (removal in 2.0)

Write the entry in the same change that makes the change, while the impact is fresh — not reconstructed from commit archaeology at release time. Breaking changes get a migration note and a deprecation window (follow the deprecation-and-migration skill); shipping the actual release is the shipping-and-launch skill's job — this section is the versioning contract that feeds it.

Common Rationalizations

Rationalization Reality
"I'll commit when the feature is done" One giant commit is impossible to review, debug, or revert. Commit each slice.
"The message doesn't matter" Messages are documentation. Future you (and future agents) will need to understand what changed and why.
"I'll squash it all later" Squashing destroys the development narrative. Prefer clean incremental commits from the start.
"Branches add overhead" Short-lived branches are free and prevent conflicting work from colliding. Long-lived branches are the problem — merge within 1-3 days.
"I'll split this change later" Large changes are harder to review, riskier to deploy, and harder to revert. Split before submitting, not after.
"I don't need a .gitignore" Until .env with production secrets gets committed. Set it up immediately.
"It's just a small fix, bump the patch" Check what consumers can observe. A behavior change they relied on is a major, whatever the diff size.
"The changelog is just the commit log" Commits are for you; the changelog is for consumers, curated by impact. Generating one from raw commits buries what matters.
"We'll write the changelog at release time" By then the impact is reconstructed from memory and half of it is missing. Write the entry with the change.

Red Flags

  • Large uncommitted changes accumulating
  • Commit messages like "fix", "update", "misc"
  • Formatting changes mixed with behavior changes
  • No .gitignore in the project
  • Committing node_modules/, .env, or build artifacts
  • Long-lived branches that diverge significantly from main
  • Force-pushing to shared branches
  • A breaking change shipped under a minor or patch version bump
  • A release with no tag, or a version number hand-edited out of sync with the tag
  • A user-facing release with no changelog entry, or a changelog that's just dumped commit messages

Verification

For every commit:

  • Commit does one logical thing
  • Message explains the why, follows type conventions
  • Tests pass before committing
  • No secrets in the diff
  • No formatting-only changes mixed with behavior changes
  • .gitignore covers standard exclusions

For every release (anything with consumers):

  • The version bump matches the change: breaking → major, additive → minor, fix → patch
  • The release is tagged, and the version is derived from the tag, not hand-edited out of sync
  • The changelog has a curated, human-readable entry grouped by impact for this version
1---
2name: git-workflow-and-versioning
3description: Structures git workflow practices. Use when making any code change. Use when committing, branching, resolving conflicts, splitting uncommitted work in a messy working tree into clean atomic commits, opening or reviewing a pull request (PR), pushing to a remote, or when you need to organize work across multiple parallel streams. Use when cutting a release, choosing a semantic version bump, tagging, or writing a changelog.
4---
5 
6# Git Workflow and Versioning
7 
8## Overview
9 
10Git is your safety net. Treat commits as save points, branches as sandboxes, and history as documentation. With AI agents generating code at high speed, disciplined version control is the mechanism that keeps changes manageable, reviewable, and reversible.
11 
12## When to Use
13 
14Always. Every code change flows through git.
15 
16## Core Principles
17 
18### Trunk-Based Development (Recommended)
19 
20Keep `main` always deployable. Work in short-lived feature branches that merge back within 1-3 days. Long-lived development branches are hidden costs — they diverge, create merge conflicts, and delay integration. DORA research consistently shows trunk-based development correlates with high-performing engineering teams.
21 
22```
23main ──●──●──●──●──●──●──●──●──●── (always deployable)
24 ╲ ╱ ╲ ╱
25 ●──●─╱ ●──╱ ← short-lived feature branches (1-3 days)
26```
27 
28This is the recommended default. Teams using gitflow or long-lived branches can adapt the principles (atomic commits, small changes, descriptive messages) to their branching model — the commit discipline matters more than the specific branching strategy.
29 
30- **Dev branches are costs.** Every day a branch lives, it accumulates merge risk.
31- **Release branches are acceptable.** When you need to stabilize a release while main moves forward.
32- **Feature flags > long branches.** Prefer deploying incomplete work behind flags rather than keeping it on a branch for weeks.
33 
34### 1. Commit Early, Commit Often
35 
36Each successful increment gets its own commit. Don't accumulate large uncommitted changes.
37 
38```
39Work pattern:
40 Implement slice → Test → Verify → Commit → Next slice
41 
42Not this:
43 Implement everything → Hope it works → Giant commit
44```
45 
46Commits are save points. If the next change breaks something, you can revert to the last known-good state instantly.
47 
48### 2. Atomic Commits
49 
50Each commit does one logical thing:
51 
52```
53# Good: Each commit is self-contained
54git log --oneline
55a1b2c3d Add task creation endpoint with validation
56d4e5f6g Add task creation form component
57h7i8j9k Connect form to API and add loading state
58m1n2o3p Add task creation tests (unit + integration)
59 
60# Bad: Everything mixed together
61git log --oneline
62x1y2z3a Add task feature, fix sidebar, update deps, refactor utils
63```
64 
65### 3. Descriptive Messages
66 
67Commit messages explain the *why*, not just the *what*:
68 
69```
70# Good: Explains intent
71feat: add email validation to registration endpoint
72 
73Prevents invalid email formats from reaching the database.
74Uses Zod schema validation at the route handler level,
75consistent with existing validation patterns in auth.ts.
76 
77# Bad: Describes what's obvious from the diff
78update auth.ts
79```
80 
81**Format:**
82```
83<type>: <short description>
84 
85<optional body explaining why, not what>
86```
87 
88**Types:**
89- `feat` — New feature
90- `fix` — Bug fix
91- `refactor` — Code change that neither fixes a bug nor adds a feature
92- `test` — Adding or updating tests
93- `docs` — Documentation only
94- `chore` — Tooling, dependencies, config
95 
96### 4. Keep Concerns Separate
97 
98Don't combine formatting changes with behavior changes. Don't combine refactors with features. Each type of change should be a separate commit — and ideally a separate PR:
99 
100```
101# Good: Separate concerns
102git commit -m "refactor: extract validation logic to shared utility"
103git commit -m "feat: add phone number validation to registration"
104 
105# Bad: Mixed concerns
106git commit -m "refactor validation and add phone number field"
107```
108 
109**Separate refactoring from feature work.** A refactoring change and a feature change are two different changes — submit them separately. This makes each change easier to review, revert, and understand in history. Small cleanups (renaming a variable) can be included in a feature commit at reviewer discretion.
110 
111### 5. Size Your Changes
112 
113Target ~100 lines per commit/PR. Changes over ~1000 lines should be split. See the splitting strategies in `code-review-and-quality` for how to break down large changes.
114 
115```
116~100 lines → Easy to review, easy to revert
117~300 lines → Acceptable for a single logical change
118~1000 lines → Split into smaller changes
119```
120 
121## Branching Strategy
122 
123### Feature Branches
124 
125```
126main (always deployable)
127 │
128 ├── feature/task-creation ← One feature per branch
129 ├── feature/user-settings ← Parallel work
130 └── fix/duplicate-tasks ← Bug fixes
131```
132 
133- Branch from `main` (or the team's default branch)
134- Keep branches short-lived (merge within 1-3 days) — long-lived branches are hidden costs
135- Delete branches after merge
136- Prefer feature flags over long-lived branches for incomplete features
137 
138### Branch Naming
139 
140```
141feature/<short-description> → feature/task-creation
142fix/<short-description> → fix/duplicate-tasks
143chore/<short-description> → chore/update-deps
144refactor/<short-description> → refactor/auth-module
145```
146 
147## Working with Worktrees
148 
149For parallel AI agent work, use git worktrees to run multiple branches simultaneously:
150 
151```bash
152# Create a worktree for a feature branch
153git worktree add ../project-feature-a feature/task-creation
154git worktree add ../project-feature-b feature/user-settings
155 
156# Each worktree is a separate directory with its own branch
157# Agents can work in parallel without interfering
158ls ../
159 project/ ← main branch
160 project-feature-a/ ← task-creation branch
161 project-feature-b/ ← user-settings branch
162 
163# When done, merge and clean up
164git worktree remove ../project-feature-a
165```
166 
167Benefits:
168- Multiple agents can work on different features simultaneously
169- No branch switching needed (each directory has its own branch)
170- If one experiment fails, delete the worktree — nothing is lost
171- Changes are isolated until explicitly merged
172 
173## The Save Point Pattern
174 
175```
176Agent starts work
177 │
178 ├── Makes a change
179 │ ├── Test passes? → Commit → Continue
180 │ └── Test fails? → Revert to last commit → Investigate
181 │
182 ├── Makes another change
183 │ ├── Test passes? → Commit → Continue
184 │ └── Test fails? → Revert to last commit → Investigate
185 │
186 └── Feature complete → All commits form a clean history
187```
188 
189This pattern means you never lose more than one increment of work. If an agent goes off the rails, `git reset --hard HEAD` takes you back to the last successful state.
190 
191## Change Summaries
192 
193After any modification, provide a structured summary. This makes review easier, documents scope discipline, and surfaces unintended changes:
194 
195```
196CHANGES MADE:
197- src/routes/tasks.ts: Added validation middleware to POST endpoint
198- src/lib/validation.ts: Added TaskCreateSchema using Zod
199 
200THINGS I DIDN'T TOUCH (intentionally):
201- src/routes/auth.ts: Has similar validation gap but out of scope
202- src/middleware/error.ts: Error format could be improved (separate task)
203 
204POTENTIAL CONCERNS:
205- The Zod schema is strict — rejects extra fields. Confirm this is desired.
206- Added zod as a dependency (72KB gzipped) — already in package.json
207```
208 
209This pattern catches wrong assumptions early and gives reviewers a clear map of the change. The "DIDN'T TOUCH" section is especially important — it shows you exercised scope discipline and didn't go on an unsolicited renovation.
210 
211## Pre-Commit Hygiene
212 
213Before every commit:
214 
215```bash
216# 1. Check what you're about to commit
217git diff --staged
218 
219# 2. Ensure no secrets
220git diff --staged | grep -i "password\|secret\|api_key\|token"
221 
222# 3. Run tests
223npm test
224 
225# 4. Run linting
226npm run lint
227 
228# 5. Run type checking
229npx tsc --noEmit
230```
231 
232Automate this with git hooks:
233 
234```json
235// package.json (using lint-staged + husky)
236{
237 "lint-staged": {
238 "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
239 "*.{json,md}": ["prettier --write"]
240 }
241}
242```
243 
244## Handling Generated Files
245 
246- **Commit generated files** only if the project expects them (e.g., `package-lock.json`, Prisma migrations)
247- **Don't commit** build output (`dist/`, `.next/`), environment files (`.env`), or IDE config (`.vscode/settings.json` unless shared)
248- **Have a `.gitignore`** that covers: `node_modules/`, `dist/`, `.env`, `.env.local`, `*.pem`
249 
250## Using Git for Debugging
251 
252```bash
253# Find which commit introduced a bug
254git bisect start
255git bisect bad HEAD
256git bisect good <known-good-commit>
257# Git checkouts midpoints; run your test at each to narrow down
258 
259# View what changed recently
260git log --oneline -20
261git diff HEAD~5..HEAD -- src/
262 
263# Find who last changed a specific line
264git blame src/services/task.ts
265 
266# Search commit messages for a keyword
267git log --grep="validation" --oneline
268```
269 
270## Release & Versioning
271 
272Commits are how *you* track change; a **version** is how your *consumers* track it. The moment anything else depends on your code — another team, a published package, a deployed client — "latest on main" stops being a sufficient answer to "what am I running, and is it safe to upgrade?" A version number and a changelog are the contract that answers it.
273 
274### Semantic Versioning
275 
276For anything with consumers, version `MAJOR.MINOR.PATCH` and let the number carry meaning:
277 
278```
279 MAJOR breaking change — consumers must change their code to upgrade
280 MINOR new functionality, backward-compatible — safe to upgrade
281 PATCH bug fix, backward-compatible — safe to upgrade
282```
283 
284The number is a promise, so make the code match it. A "patch" that changes behavior consumers relied on is a major change wearing a disguise (Hyrum's Law — see the `api-and-interface-design` skill). When unsure whether a change is breaking, assume it is; a surprise major is far cheaper than a broken consumer.
285 
286### Tag the release, and let the tag be the source of truth
287 
288A release is an immutable point in history, not a moving branch. Tag it so it can always be reproduced:
289 
290```bash
291git tag -a v1.4.0 -m "Release 1.4.0"
292git push origin v1.4.0
293```
294 
295Derive the version from the tag rather than hand-editing it in scattered files, so the artifact, the tag, and the changelog can never disagree.
296 
297### Keep a changelog written for humans
298 
299A changelog is not `git log`. It's the curated, consumer-facing answer to "what changed and do I care?" — grouped by `Added / Changed / Fixed / Deprecated / Removed / Security`, newest on top, every entry phrased around user impact, not internal mechanics.
300 
301```markdown
302## [1.4.0] - 2025-06-12
303### Added
304- Bulk task import via CSV
305### Fixed
306- Timezone drift in recurring task due dates
307### Deprecated
308- `GET /v1/tasks/all` — use the paginated `GET /v1/tasks` (removal in 2.0)
309```
310 
311Write the entry in the same change that makes the change, while the impact is fresh — not reconstructed from commit archaeology at release time. Breaking changes get a migration note and a deprecation window (follow the `deprecation-and-migration` skill); shipping the actual release is the `shipping-and-launch` skill's job — this section is the versioning contract that feeds it.
312 
313## Common Rationalizations
314 
315| Rationalization | Reality |
316|---|---|
317| "I'll commit when the feature is done" | One giant commit is impossible to review, debug, or revert. Commit each slice. |
318| "The message doesn't matter" | Messages are documentation. Future you (and future agents) will need to understand what changed and why. |
319| "I'll squash it all later" | Squashing destroys the development narrative. Prefer clean incremental commits from the start. |
320| "Branches add overhead" | Short-lived branches are free and prevent conflicting work from colliding. Long-lived branches are the problem — merge within 1-3 days. |
321| "I'll split this change later" | Large changes are harder to review, riskier to deploy, and harder to revert. Split before submitting, not after. |
322| "I don't need a .gitignore" | Until `.env` with production secrets gets committed. Set it up immediately. |
323| "It's just a small fix, bump the patch" | Check what consumers can observe. A behavior change they relied on is a major, whatever the diff size. |
324| "The changelog is just the commit log" | Commits are for you; the changelog is for consumers, curated by impact. Generating one from raw commits buries what matters. |
325| "We'll write the changelog at release time" | By then the impact is reconstructed from memory and half of it is missing. Write the entry with the change. |
326 
327## Red Flags
328 
329- Large uncommitted changes accumulating
330- Commit messages like "fix", "update", "misc"
331- Formatting changes mixed with behavior changes
332- No `.gitignore` in the project
333- Committing `node_modules/`, `.env`, or build artifacts
334- Long-lived branches that diverge significantly from main
335- Force-pushing to shared branches
336- A breaking change shipped under a minor or patch version bump
337- A release with no tag, or a version number hand-edited out of sync with the tag
338- A user-facing release with no changelog entry, or a changelog that's just dumped commit messages
339 
340## Verification
341 
342For every commit:
343 
344- [ ] Commit does one logical thing
345- [ ] Message explains the why, follows type conventions
346- [ ] Tests pass before committing
347- [ ] No secrets in the diff
348- [ ] No formatting-only changes mixed with behavior changes
349- [ ] `.gitignore` covers standard exclusions
350 
351For every release (anything with consumers):
352 
353- [ ] The version bump matches the change: breaking → major, additive → minor, fix → patch
354- [ ] The release is tagged, and the version is derived from the tag, not hand-edited out of sync
355- [ ] The changelog has a curated, human-readable entry grouped by impact for this version
356 

Discussion

Alternatives

Also in CI/CD & releasesSee all 533 in Development →