◉ANTIGRAVITY LABJP
Articles/Tips & Best Practices
✦ Tips & Best Practices/2026-04-27Intermediate

Half-Automating CHANGELOG.md From git log With Antigravity — A Solo Developer's Decision Log

You opened your code from six months ago and forgot why you made that change. A common solo-dev problem. Here's a half-automated workflow that uses Antigravity to draft CHANGELOG.md entries straight from your git log, with a working shell script.

Antigravity401CHANGELOGgit16solo devworkflow57documentation8

A few weeks ago, I opened the auth middleware I had written six months earlier and could not, for the life of me, remember why I had switched httpOnly: true back to httpOnly: false. The commit message was a single line: fix: cookie issue. git blame led me to a PR description that no longer existed because I had squashed and force-pushed my own branch (yes, on my own project — old habits). Thirty minutes of digging through Slack DMs to myself eventually surfaced the answer: a specific browser extension was clobbering session cookies with httpOnly: true.

After enough of those archaeology sessions, I finally accepted that solo developers also need a CHANGELOG. The problem was that writing one from scratch every release never lasts. What sticks for me is a half-automated flow: feed the git log to Antigravity, ask it to draft a CHANGELOG section, then edit it by hand. The "half" turns out to matter — it keeps me honest without becoming a chore.

Why a Solo Project Still Needs CHANGELOG.md

The usual argument against a CHANGELOG on a solo project is "I'm the only one reading it, so I'll just remember." I told myself that for years. The shift happened when I started thinking of future-me as a different person.

Three months later, refactors blur. Six months later, SDK upgrades feel arbitrary. A year later, your data model has decisions baked into it that you cannot reconstruct from the code alone. CHANGELOG.md is, more than anything, an index that makes those past selves legible to your present self.

The reason most solo CHANGELOGs die is timing. If you only update it at release time, you face a week of commits to triage, and you put it off. The flip that worked for me was deciding to update CHANGELOG.md every Saturday morning, ten minutes flat, even when there is nothing to release.

Loose Conventional Commits as Raw Material

For Antigravity to draft anything useful, your commit messages need a small amount of structure. Full Conventional Commits (feat(scope): description with footer) is overkill for solo work — at least, it is for me. The point of a convention is to lower friction, and a strict convention can do the opposite.

Here is the loose set I actually use:

  • feat: user-visible new functionality
  • fix: user-visible bug fix
  • refactor: code reshuffling that does not change behavior
  • chore: dependencies, build config, CI plumbing
  • Anything else: free-form (not enforced)

That last rule matters. Tired Friday-night commits should not be blocked by your tooling. Antigravity normalizes the rough edges later, so the human side stays cheap.

If you want to pin down what gets staged and how messages are written, the allowlist and message rules in Keeping Stray Files Out of Agent Commits With a Staging Scope and a Message Convention drop in as they are. Personally I stop short of enforcement because losing a few "wip" commits is worth the long-term motivation.

Designing the Prompt You Hand to Antigravity

Asking Antigravity to "turn this git log into a changelog" tends to produce vague summaries. Specificity comes from naming the input shape, the output shape, and the things you do not want it to do.

Here is the prompt I use, kept in .agentic/prompts/changelog.md:

You are a CHANGELOG editor for a solo developer.

From the git log below, extract only the changes that matter from a USER's
perspective (feat / fix / important refactor) and emit a Markdown block to
append to the Unreleased section of CHANGELOG.md.

Rules:
- Skip internal-only work (chore, internal refactor) unless it changed behavior.
- Collapse multiple commits on the same feature into a single line.
- Phrase each entry as a change the user can FEEL, not as a commit subject.
- Do NOT speculate about user impact. If the log doesn't say "performance",
  do not write "improved performance."
- End every entry with the hash of the commit it is based on, like (a3b8f12).
  Several commits: (a3b8f12, e2c0a91)

Output format:
### Added
- ... (hash)

### Fixed
- ... (hash)

### Changed
- ... (hash)

The "do not speculate" rule earns its keep. Without it, Antigravity sometimes adds polite-sounding lines like "Improved overall performance" when the actual commits never mentioned performance at all. CHANGELOG entries you cannot defend are worse than missing entries.

Implementation: A Shell Script You Can Steal in Five Minutes

Save the script below as scripts/draft-changelog.sh. It collects everything since the latest tag (two weeks if there is no tag) in a shape Antigravity can ingest cleanly.

#!/usr/bin/env bash
# scripts/draft-changelog.sh
# Collect a git range into .changelog-draft-input.md for Antigravity to read.
# No argument: everything since the latest tag (or two weeks if there is no tag).
# With an argument: passed straight to --since, e.g. "1 month ago".
 
set -euo pipefail
 
OUTPUT_FILE=".changelog-draft-input.md"
 
if [ $# -ge 1 ]; then
  RANGE_ARGS=(--since="$1"); LABEL="since $1"
elif LAST_TAG="$(git describe --tags --abbrev=0 2>/dev/null)"; then
  RANGE_ARGS=("${LAST_TAG}..HEAD"); LABEL="${LAST_TAG}..HEAD"
else
  RANGE_ARGS=(--since="2 weeks ago"); LABEL="since 2 weeks ago"
fi
 
{
  echo "## git log: ${LABEL}"
  echo
  echo '```'
  # --no-merges drops merge commits; %h stays because we match entries against it later
  git log --no-merges "${RANGE_ARGS[@]}" --pretty=format:'%h %s%n%b%n---'
  echo
  echo '```'
} > "$OUTPUT_FILE"
 
echo "✓ Wrote: $OUTPUT_FILE (${LABEL})"
echo "  → Open Antigravity, paste this file plus the CHANGELOG prompt into the Side Panel, run it."

A trimmed example of what the file looks like:

## git log: v1.4.0..HEAD

```
a3b8f12 feat: add Stripe webhook handler for subscription events
Verified signatures with Cloudflare Workers SubtleCrypto.
---
e2c0a91 fix: prevent double-charge when user retries Stripe Checkout
Used the session id as the idempotency key.
---
(truncated)
```
✓ Wrote: .changelog-draft-input.md (v1.4.0..HEAD)
  → Open Antigravity, paste this file plus the CHANGELOG prompt into the Side Panel, run it.

Drop that file plus the prompt into Antigravity's Side Panel. You will get a draft ### Added / Fixed / Changed block. I run it through the check in the next section, read it once, delete the lines that feel padded, rephrase one or two that read awkwardly, and paste the result under ## [Unreleased] in CHANGELOG.md. Ten minutes, every Saturday.

A pleasant side effect: scrolling through your own log forces you to remember work you forgot to mention to users. Most of my "release notes" tweets now come out of this Saturday window, not out of release-eve panic.

Choosing the Range, and Checking the Draft Against the Log

For a while I ran everything on --since="2 weeks ago". Because I start the Saturday session at a slightly different time each week, the boundary drifted: the last commit of the previous week showed up twice, or a commit fell through the gap. These days I use "since the latest tag" whenever there is a release boundary, and a date window only when there is not.

RangeFits whenWhere it bites
--since="2 weeks ago"Small apps with no tags, reviewed weeklyThe boundary shifts with the run time, so commits repeat or slip through
v1.4.0..HEADYou cut the CHANGELOG per releaseA forgotten tag balloons the range, and the draft gets long and loose
After the last hash of the previous draftNo tags, but you want a strict weekly cadenceYou have to store that hash yourself

The range was the smaller problem. One Saturday the draft came back with a line saying login stability had improved. The only plausible commit was fix: typo in error message. I caught it only because I happened to remember that week's commits; in a week I did not remember, I would have pasted it under [Unreleased] as it was.

Writing "do not speculate" in the prompt never brought the rate to zero, so I closed the gap mechanically instead. The prompt makes Antigravity end each entry with a commit hash, and a small script checks that every hash really exists in the range.

#!/usr/bin/env bash
# scripts/verify-changelog.sh
# Check that every entry in a draft (stdin) cites a commit that really exists in the range.
# Usage: bash scripts/verify-changelog.sh v1.4.0..HEAD < draft.md
 
set -euo pipefail
 
RANGE="${1:?usage: verify-changelog.sh <range> < draft.md}"
ALL="$(git rev-list --no-merges "$RANGE")"
status=0
 
while IFS= read -r line; do
  case "$line" in "- "*) ;; *) continue ;; esac
 
  hashes="$(printf '%s' "$line" | grep -oE '[0-9a-f]{7,40}' || true)"
  if [ -z "$hashes" ]; then
    echo "NO-HASH : $line"; status=1; continue
  fi
 
  for h in $hashes; do
    full="$(git rev-parse --verify --quiet "${h}^{commit}" || true)"
    if [ -z "$full" ] || ! printf '%s\n' "$ALL" | grep -qx "$full"; then
      echo "UNKNOWN : ${h} <- $line"; status=1
    fi
  done
done
 
[ "$status" -eq 0 ] && echo "OK: every entry maps to a commit in the range"
exit "$status"

Save the draft as draft.md and run bash scripts/verify-changelog.sh v1.4.0..HEAD < draft.md. A line without a hash prints NO-HASH; a hash that is not in the range, or does not exist, prints UNKNOWN. Either one makes the exit code 1.

What this guarantees is only that the cited commit exists. Whether that commit actually supports the sentence is still my call at the end. Even so, the job shrank from reading the whole draft with suspicion to comparing a hash against a line, and that is a much lighter thing to do on a Saturday morning.

If a line cannot point at a commit, it does not go into the CHANGELOG, whether a person or an AI wrote it. Once a script held that line for me, the ten minutes stopped being a time for doubting and became a time for comparing and fixing.

What I Deliberately Leave Out

Knowing what not to put in the CHANGELOG is half of the work. Solo developers who try to capture everything either burn out or end up with a log so noisy nobody (including future-them) reads it.

The categories I drop without hesitation:

  • Dependency bumps that did not visibly change anything. A patch-level bump of an internal lint rule belongs in package-lock.json, not in CHANGELOG.md. If the bump fixed a security advisory or introduced a breaking change, that is different — it gets a dedicated entry.
  • Internal refactors with no user-facing effect. Renaming a private function from parseRow to parseCSVRow is invisible to users. CHANGELOG is a user-facing document; engineering hygiene lives in the commit history itself.
  • Experiments that got reverted in the same week. If you tried a feature, did not like it, and removed it before any release, there is no need to document the round trip. That is what git reflog is for.
  • CI and tooling. Switching from GitHub Actions to a different runner is interesting to you, irrelevant to the user. Goes in a separate INTERNAL_NOTES.md if you must.

What I always include, even when tempted to skip:

  • Anything that changes a default. Even a one-line config change becomes a support ticket if a user notices the new behavior. Documenting the change saves the future reply.
  • Anything that affects pricing, billing, or auth. These are the categories where users feel betrayed when the change appears unannounced. Always log them, even if "the user" right now is just you.
  • Anything that changes data shape. New fields on stored records, removed fields, renamed enum values — these have a long tail of consequences. CHANGELOG is the cheapest way to leave breadcrumbs for the migration you will eventually need to write.

This filter is also exactly the lens I tell Antigravity to use, which is why the prompt above explicitly asks for "user-visible" changes only.

Three Things That Kept the Habit Alive

After three months on this loop, I can name the patterns that almost killed it. Pre-empting them helps.

Stop trying to capture every commit.

In the early weeks I felt obligated to translate every line of git log into something CHANGELOG-worthy. Internal refactors and CI tweaks do not belong in a user-facing changelog. When Antigravity drops them, let them stay dropped.

Saturday morning, not Friday evening.

Friday afternoon you are tired and sloppy. Saturday morning, with coffee and no Slack pings, the user-visible rephrasing comes out cleaner. Find your own equivalent — the point is to not write CHANGELOG entries while running on fumes.

Make end-of-month "Unreleased → vX.Y.Z" a small ritual.

Each Saturday you grow [Unreleased]. Once a month you cut it into a real version. I do this at a café, with a different coffee than my Saturday usual. It marks the boundary between "operating" and "shipping" in a way that is small but specific. CHANGELOGs without a release ritual quietly fall behind.

If you want CHANGELOG.md to feed back into how you actually work — for example, having Antigravity reference it as required reading before any large refactor — I cover the .agentic/rules setup in Turning Gut-Feel Judgments Into Rules Agents Can Follow. It is a natural next step once you have a few months of CHANGELOG history accumulated.

One Concrete Next Step

If you already have a git history, you can try this today. Run git log --no-merges --since="2 weeks ago" --oneline and just look at it. Notice how many lines genuinely matter to a user, and how many do not. From there, the ten lines of shell above are enough to seed the loop. Next Saturday, your CHANGELOG.md starts growing on its own.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

✦ Tips2026-09-22
Don't Leave Your Chat Records to the Export Button — Three Routes for the Night It Does Nothing
Three ways to keep a record of your Antigravity conversations when the Export button does nothing at all: how to tell a broken button from a silent success, how to locate the conversation file in your own environment by modification time, and how to decide what to keep before the session ends.
⟐ Editor View2026-09-03
How much of Antigravity's generated commit message I actually keep
Where the Review pane's generated commit message stops being enough, and where I start writing. A prepare-commit-msg hook that adds a Why trailer, plus measured behaviour during rebase, cherry-pick and revert.
✦ Tips2026-10-27
A 60-Line PreToolUse Hook That Stops an Agent's rm -rf Before It Runs
How to block rm -rf and git push --force before they execute, using an Antigravity CLI PreToolUse hook. Includes what a one-line grep missed across 16 test commands.