-
-
Notifications
You must be signed in to change notification settings - Fork 7
Documentation & Change Control
TL;DR: Every project must maintain a
README.mdand aCHANGELOG.md. How the changelog is written depends on the project: one that uses chlog writes a YAML fragment per change under.changes/unreleased/and never editsCHANGELOG.mdby hand; one that has not adopted chlog edits the[Unreleased]section ofCHANGELOG.mddirectly. Either way the changelog entry ships with the change -- always -- and README and other docs (e.g.,.github/copilot-instructions.md) are updated whenever behavior, configuration, or architecture changes.
Documentation and change control are integral parts of the engineering workflow, not afterthoughts. A well-maintained changelog and README provide traceability for stakeholders, reduce onboarding friction for new team members, and ensure that the state of the project is always understandable from its documentation alone.
Every change introduced to a project must include updates to the relevant documentation files. This is enforced as part of the development workflow, not as a separate task.
Every project must contain at minimum:
| File | Purpose | Required when | Update Frequency |
|---|---|---|---|
README.md |
Describes the project, its usage, setup, and architecture | Always | When behavior, configuration, CLI, or setup changes |
CONTRIBUTING.md |
Guides contributors on prerequisites, workflow, and standards | Always | When prerequisites, workflow, or structure changes |
CHANGELOG.md |
Records all notable changes, organized by version | Always | See Changelog Standard |
.chlog.yaml |
chlog's configuration -- the kinds and the bump each infers, and the marker CI detects | When the project uses chlog | When the kinds change (rarely) |
.changes/unreleased/ |
One YAML changelog fragment per change, written by chlog new
|
When the project uses chlog | Every change (always) |
.github/copilot-instructions.md |
AI assistant context for the project structure and workflows | Always | When architecture, commands, or workflow changes |
Templates are available for standardized project setup:
- README Template -- copy and customize for new projects
- CONTRIBUTING Template -- copy and customize for new projects
- CHANGELOG Formatting -- capitalization and backtick rules for a changelog entry
Every project keeps a CHANGELOG.md in the Keep a Changelog
format, versioned with Semantic Versioning. What differs between projects is
who writes that file:
| Mode | Where a change is recorded | Who writes CHANGELOG.md
|
|---|---|---|
| With chlog | A YAML fragment under .changes/unreleased/
|
chlog merge, at release time |
| Without chlog | A bullet under [Unreleased] in CHANGELOG.md
|
The author of the change, by hand |
Both modes produce the same released file and obey the same writing rules. Only the mechanics differ, so a contributor moving between repositories has one thing to check first: which mode this repository is in.
Look at the project root:
| Signal at the project root | Mode |
|---|---|
.chlog.yaml (or .chlog.yml) exists |
chlog -- write a fragment |
No config file, but .changes/ exists |
chlog -- write a fragment, and add the missing config |
| Neither exists |
No chlog -- edit [Unreleased] in CHANGELOG.md
|
The shared rios0rios0/pipelines basic-checks gate
decides the same way, and it keys on .chlog.yaml specifically. A project that adopts chlog
must therefore commit that file: with the fragments in place but no config, CI still asks for a
CHANGELOG.md diff and the fragment does not satisfy it. Its values repeating chlog's built-in
defaults is not a reason to delete it -- the file is the marker that flips the gate, whatever it
contains.
chlog is a single Go binary that compiles per-change
fragments into CHANGELOG.md:
go install github.com/luizjhonata/chlog@latestgo install builds from source, so it needs a Go toolchain on the contributor's machine -- which a
Java, Python, or TypeScript project has no reason to require. Those projects should take the
prebuilt binary for their platform from the
releases page instead; chlog publishes one for
Linux, macOS, and Windows on both amd64 and arm64. Either way the tool is a single self-contained
binary, and the project itself never gains a Go dependency.
Every change writes its own YAML file under .changes/unreleased/:
chlog new --kind Added --body "added JavaScript updater supporting npm, yarn, and pnpm projects"
chlog new --kind Changed --breaking --body "**BREAKING CHANGE:** changed `Input` to take its value from props"which produces a file the tool names for you:
kind: 'Added'
body: 'added JavaScript updater supporting npm, yarn, and pnpm projects'
time: '2026-01-15T09:41:02.117823941Z'This buys the one thing a single shared file cannot give: two branches each adding an entry no
longer touch the same lines, so a rebase that used to conflict on CHANGELOG.md now conflicts on
nothing.
Never edit CHANGELOG.md by hand in this mode. The only writer is chlog merge, at release time.
The kind is the Keep a Changelog category, and it carries the version bump a release infers from it:
| Kind | When to Use | Infers |
|---|---|---|
| Added | New features, new files, new capabilities | minor |
| Changed | Modifications to existing functionality | minor |
| Deprecated | Features that will be removed in a future version | minor |
| Removed | Features that were removed | minor |
| Fixed | Bug fixes | patch |
| Security | Vulnerability fixes | patch |
No kind infers a major. Under SemVer a major bump means a backward-incompatible change, which is
a property of the change and not of its category -- so it is signalled per fragment with
chlog new --breaking, and never inferred from a label. Keep the **BREAKING CHANGE:** prefix in
the body as well: the flag drives the version, the prefix tells the reader.
Every project that uses chlog carries a .chlog.yaml at its root. Spell chlog's defaults out rather
than relying on them: the file is what CI detects, and it makes the kinds and their bump levels
readable without going to the tool. It follows the YAML conventions like
every other YAML file -- the .yaml extension, and single quotes around every string:
changesDir: '.changes'
unreleasedDir: 'unreleased'
changelogPath: 'CHANGELOG.md'
versionFormat: '## [{{.Version}}] - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}'
changeFormat: '- {{.Body}}'
kinds:
- label: 'Added'
auto: 'minor'
- label: 'Changed'
auto: 'minor'
- label: 'Deprecated'
auto: 'minor'
- label: 'Removed'
auto: 'minor'
- label: 'Fixed'
auto: 'patch'
- label: 'Security'
auto: 'patch'The double quotes inside versionFormat belong to the Go template, not to YAML: a single-quoted
scalar takes them literally, which is exactly what the template needs.
CHANGELOG.md keeps its familiar shape -- an empty [Unreleased] heading and one section per
released version:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This file is not edited by hand. Every change writes its own fragment under
`.changes/unreleased/` with [chlog](https://github.com/luizjhonata/chlog), and a release
compiles the pending fragments into a version section here.
## [Unreleased]
## [1.0.0] - 2026-01-15
### Added
- added initial project setup with Clean ArchitectureThe pending fragments become a version section at release time:
- Create a branch
chore/bump-x.y.z. - Run
chlog batch auto && chlog merge.batch autoderives the version from the pending kinds and theirbreakingflags;mergefolds the compiled batch intoCHANGELOG.mdand empties.changes/unreleased/. - Open a Pull Request targeting
main. - After merge, create a Git tag for the version.
AutoBump performs steps 2 and 3 -- see Automation with AutoBump.
The shared rios0rios0/pipelines basic-checks gate is
chlog-aware: when .chlog.yaml is present it requires a new fragment on an ordinary branch, and
flips to requiring an updated CHANGELOG.md on a chore/bump-* (or bump/*) branch, where
chlog merge has already consumed the fragments. No per-repository CI job is needed.
chlog hook install --local runs the same check on every commit in a local clone, and chlog check
runs it on demand.
chlog ai setup injects a marked block (<!-- chlog:start --> ... <!-- chlog:end -->) into
CLAUDE.md and .github/copilot-instructions.md telling the assistant to write a fragment on every
change and never to touch CHANGELOG.md. Re-running the command updates the block in place, so it
is safe to run again after an upgrade. The injected block is conditional on the repository actually
using chlog, so it is harmless in a repository that has not adopted it yet.
A project that has not adopted chlog keeps the same file, written by hand. Everything a change adds
goes under [Unreleased], grouped by category:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- added new feature X that does Y
### Changed
- changed behavior of Z to handle edge case W
### Fixed
- fixed a bug where A caused B
## [1.0.0] - 2026-01-15
### Added
- added initial project setup with Clean ArchitectureThe categories are the same ones chlog calls kinds:
| Category | When to Use |
|---|---|
| Added | New features, new files, new capabilities |
| Changed | Modifications to existing functionality |
| Deprecated | Features that will be removed in a future version |
| Removed | Features that were removed |
| Fixed | Bug fixes |
| Security | Vulnerability fixes |
A backward-incompatible change is marked in the entry itself, since there is no --breaking flag to
carry it:
- **BREAKING CHANGE:** changed `Input` to take its value from propsAll in-progress changes go under [Unreleased]. When a release is cut:
- Create a branch
chore/bump-x.y.z. - Move the entries from
[Unreleased]to a new version heading with the release date. - Open a Pull Request targeting
main. - After merge, create a Git tag for the version.
With no .chlog.yaml at the root, the basic-checks gate falls back to requiring that the PR touch
CHANGELOG.md. A "CHANGELOG.md was NOT modified" failure therefore means the project is in this
mode: add the entry under [Unreleased] rather than reaching for chlog new.
These govern the text of the entry -- the --body of a fragment, or the bullet typed under
[Unreleased]. They are identical in both modes:
- Write for humans, not machines. Describe what changed and why, not implementation details.
- Use simple past tense. "added", "changed", "fixed", "removed" -- consistent with the commit message standard.
-
Start each entry with a lowercase verb. Example:
added automatic Dockerfile image tag update. -
Be specific. Bad:
updated dependencies. Good:added JavaScript updater supporting npm, yarn, and pnpm projects. - Link to issues or PRs when the change is non-trivial.
- Group related changes in a single entry rather than listing every file touched.
- One entry per change, not per commit. A branch that does one thing carries one entry.
See CHANGELOG Formatting for capitalization and backtick rules.
Moving a project from the hand-written file to fragments:
- Release or carry over whatever sits under
[Unreleased]. Anything left there is invisible tochlog batch, which only reads fragments -- so either cut a release first, or re-create each pending entry withchlog new. - Add
.chlog.yamlat the root, as spelled out in Configuration. This is the step that flips CI; without it the gate keeps asking for aCHANGELOG.mddiff. - Leave the released sections of
CHANGELOG.mduntouched -- chlog appends above them. - Add the header note to
CHANGELOG.mdsaying the file is generated, so the next contributor does not hand-edit it. - Run
chlog ai setupto updateCLAUDE.mdand.github/copilot-instructions.md. - Update
CONTRIBUTING.md: add chlog to the prerequisites and replace the "update CHANGELOG.md" step withchlog new.
The README.md must accurately describe the current state of the project. Update it whenever:
- A new feature changes how users interact with the project.
- CLI commands, flags, or configuration options are added, changed, or removed.
- Setup instructions, prerequisites, or environment requirements change.
- The project structure or architecture changes significantly.
- New dependencies or integrations are introduced.
| Section | Purpose |
|---|---|
| Title and description | One-line summary of what the project does |
| Quick start / Installation | How to get running in under 5 minutes |
| Usage | Commands, configuration, and examples |
| Architecture / Project structure | High-level overview of directories and layers |
| Development | How to build, test, and contribute |
| References | Links to external documentation |
Projects that use AI-assisted development (GitHub Copilot, Cursor, etc.) should maintain a .github/copilot-instructions.md file. This file provides the AI with project-specific context about:
- Project purpose and architecture
- Build, test, and lint commands with expected timings
- Repository structure and key files
- Development workflow and validation steps
- Testing infrastructure and conventions
Update this file whenever the development workflow, architecture, or key commands change.
Documentation updates must be part of the same commit or PR that introduces the change:
- Write the code change.
-
Record the change in the changelog --
chlog new --kind <Kind> --body "..."when the project uses chlog, or a bullet under[Unreleased]inCHANGELOG.mdwhen it does not. See Deciding Which Mode Applies. -
Update
README.md-- if the change affects usage, setup, or architecture. -
Update
.github/copilot-instructions.md-- if the change affects build commands, project structure, or development workflow. - Commit everything together. Documentation and code ship as one unit.
Never merge a PR that introduces user-facing or architectural changes without the corresponding documentation update.
AutoBump is a CLI tool that automates the release step of the changelog workflow. When the pending changes are ready to ship, AutoBump detects the project language, compiles the pending entries into a new versioned section with the current date, updates language-specific version files (e.g., go.mod, package.json, pyproject.toml, build.gradle), commits, pushes, and opens a merge/pull request -- all in a single command.
It handles both modes: it detects the chlog layout (a .chlog.yaml or the fragment directory), reads the fragments under .changes/unreleased/ directly, and consumes them -- otherwise it reads the [Unreleased] section. Adopting chlog therefore changes nothing about the release flow.
It supports Go, Java, Python, TypeScript, and C# projects, with automatic language detection, and works across GitHub, GitLab, and Azure DevOps.
AutoBump does not replace the discipline of writing changelog entries. The fragments (or the [Unreleased] bullets), README.md, and other documentation files must already exist and be maintained by the team as part of every change. AutoBump only automates the versioning and release ceremony -- not the content creation.
Because AutoBump opens its own release pull requests, those commits are exempt from the ticket reference every other commit carries -- see Ticket Reference in the Git Flow guide.