You’ve just finished a marathon refactoring - perhaps splitting a monolithic script into proper modules-and now you need to write the release notes. You could feed an AI a messy git log, but if you want high-fidelity summaries that actually understand your architecture, you need to provide better context.
…and is pretty good at them too!
Instead of manually describing changes or hoping it can interpret my ChangeLog, I’ve automated the production of three ephemeral “Sidecar” assets. These are generated on the fly, uploaded to the LLM, and then purged after analysis - no storage required.
.lst): A simple list of every file touched,
ensuring the AI knows the exact scope of the release..diffs): A unified diff (using git diff
--no-ext-diff) that provides the “what” and “why” of every code
change..tar.gz): This is the “secret sauce.” It contains
the full source of the changed files, allowing the AI to see the
final implementation - not just the delta.Makefile ImplementationIf you’ve read any of my blog
posts you
know I’m a huge Makefile fan. To automate this I’m naturally going
to add a recipe to my Makefile or Makefile.am.
First, we explicitly set the shell to /usr/bin/env bash to ensure features
like brace expansion work consistently across all dev environments.
# Ensure a portable bash environment for advanced shell features
SHELL := /usr/bin/env bash
.PHONY: release-notes clean-local
# Default to the version file, but allow command-line overrides
VERSION ?= $(shell cat VERSION)
release-notes:
@curr_ver=$(VERSION); \
last_tag=$$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1); \
diffs="release-$$curr_ver.diffs"; \
diff_list="release-$$curr_ver.lst"; \
diff_tarball="release-$$curr_ver.tar.gz"; \
echo "Comparing $$last_tag to current $$curr_ver..."; \
git diff --no-ext-diff "$$last_tag" "$$curr_ver" > "$$diffs"; \
git diff --name-only --diff-filter=AMR "$$last_tag" "$$curr_ver" > "$$diff_list"; \
tar -cf - -T "$$diff_list" --transform "s|^|release-$$curr_ver/|" | gzip > "$$diff_tarball"; \
ls -alrt release-$$curr_ver*
clean-local:
@echo "Cleaning ephemeral release assets..."
rm -f release-*.{tar.gz,lst,diffs}
/usr/bin/env bash): We avoid hardcoding
paths to ensure the script finds the correct Bash path on macOS,
Linux, or inside a container.VERSION ?=): This allows the
“pre-flight” trick: running make release-notes VERSION=HEAD to
iterate on notes before you’ve actually tagged the release.--sort=-v:refname): Using v:refname
forces Git to use semantic versioning logic (so 1.10.0 correctly
follows 1.2.0), while the glob pattern filters out “noisy”
non-version tags.--diff-filter=AMR): This ensures the tarball
only includes files that actually exist (Added, Modified, or
Renamed). If a release deleted a file, this filter prevents tar
from erroring out when it can’t find the missing file on disk.clean-local): remove the ephemeral artifacts
using bash expansionOnce your assets are generated, upload them to AI and use a prompt that enforces your specific formatting standards.
The Prompt:
I have uploaded a file manifest (`.lst`), a unified diff
(`.diffs`), and a tarball of the source code (`.tar.gz`). Please
analyze these to identify the primary themes of this
release. Generate a release entry using this protocol:
1. Separate versions with 71 dashes.
2. Include a high-level executive summary paragraph.
3. Indent bullet points exactly 21 spaces to maintain a vertical column.
4. Group changes under category labels like `Core Bedrock:` or `Miniserver:`.
5. Provide plain-text ASCII only inside a code block.
In a recent dry run for Bedrock 3.8.8, this exact workflow allowed the
AI to correctly identify that bedrock-miniserver.pl.in was
refactored into Bedrock::MiniServer and Bedrock::MiniServer::CLI,
while accurately noting that Bedrock::Error moved to the
Bedrock-Core distribution.
In actuality, version 3.8.8 focuses on the primary goals of MiniServer maturation (more batteries included), Apache decoupling, and distribution hygiene. Here’s AI’s version:
-----------------------------------------------------------------------
Bedrock 3.8.8
-----------------------------------------------------------------------
This release focuses on the maturation of the MiniServer component,
decoupling core Bedrock logic from Apache-specific dependencies, and
improving distribution hygiene. Key architectural changes include
refactoring the MiniServer into modular components and moving
exception handling into the Bedrock-Core distribution.
2026-03-17 - 3.8.8 - MiniServer Maturation and Apache Decoupling
Miniserver:
- Refactored bedrock-miniserver.pl into modular
Bedrock::MiniServer and Bedrock::MiniServer::CLI.
- Implemented zero-config scaffolding to
automatically create application trees.
- Integrated full Bedrock configuration pipeline
for parity with Apache environments.
- Updated bedrock_server_config to support both
getter and setter operations.
Core:
- Moved Bedrock::Error and Bedrock::Exception to
the Bedrock-Core distribution.
- Introduced Bedrock::FauxHandler as a production-
ready alias for test handlers.
- Added dist_dir() to BLM::Startup::Bedrock to
expose distribution paths to templates.
Fixes:
- Demoted Apache-specific modules (mod_perl2,
Apache2::Request) to optional recommendations.
- Improved Bedrock::Test::FauxHandler to handle
caller-supplied loggers and safe destruction.
As I mentioned in a response to a recent Medium article, AI can be an accelerator for seasoned professionals. You’re not cheating. You did the work. AI does the wordsmithing. You edit, add color, and ship. What used to take 30 minutes now takes 3. Now that’s working smarter, not harder!
Add this to the top of your Makefile
SHELL := /usr/bin/env bash
# Default to the version file, but allow command-line overrides
VERSION ?= $(shell cat VERSION)
Copy this to a file named release-notes.mk
.PHONY: release-notes clean-local
release-notes:
@curr_ver=$(VERSION); \
last_tag=$$(git tag -l '[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1); \
diffs="release-$$curr_ver.diffs"; \
diff_list="release-$$curr_ver.lst"; \
diff_tarball="release-$$curr_ver.tar.gz"; \
echo "Comparing $$last_tag to current $$curr_ver..."; \
git diff --no-ext-diff "$$last_tag" "$$curr_ver" > "$$diffs"; \
git diff --name-only --diff-filter=AMR "$$last_tag" "$$curr_ver" > "$$diff_list"; \
tar -cf - -T "$$diff_list" --transform "s|^|release-$$curr_ver/|" | gzip > "$$diff_tarball"; \
ls -alrt release-$$curr_ver*
clean-local:
@echo "Cleaning ephemeral release assets..."
rm -f release-*.{tar.gz,lst,diffs}
Then add release-notes.mk to your Makefile
include release-notes.mk
Previous post: RIP nginx - Long Live Apache