Skip to content

Documentation & Change Control

Felipe Rios edited this page Oct 4, 2024 · 1 revision

Documentation & Change Control

TL;DR: Every project must maintain a README.md and a CHANGELOG.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 edits CHANGELOG.md by hand; one that has not adopted chlog edits the [Unreleased] section of CHANGELOG.md directly. 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.

Overview

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.

Required Documentation Files

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:

Changelog Standard

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.

Deciding Which Mode Applies

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.

Changelog with chlog

chlog is a single Go binary that compiles per-change fragments into CHANGELOG.md:

go install github.com/luizjhonata/chlog@latest

go 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.

One Fragment per Change

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.

Kinds

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.

Configuration

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.

The Generated File

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 Architecture

Releasing

The pending fragments become a version section at release time:

  1. Create a branch chore/bump-x.y.z.
  2. Run chlog batch auto && chlog merge. batch auto derives the version from the pending kinds and their breaking flags; merge folds the compiled batch into CHANGELOG.md and empties .changes/unreleased/.
  3. Open a Pull Request targeting main.
  4. After merge, create a Git tag for the version.

AutoBump performs steps 2 and 3 -- see Automation with AutoBump.

CI Enforcement

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.

AI Assistants

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.

Changelog without chlog

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 Architecture

Change Categories

The 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 props

The [Unreleased] Section

All in-progress changes go under [Unreleased]. When a release is cut:

  1. Create a branch chore/bump-x.y.z.
  2. Move the entries from [Unreleased] to a new version heading with the release date.
  3. Open a Pull Request targeting main.
  4. After merge, create a Git tag for the version.

CI Enforcement

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.

Writing Rules

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.

Adopting chlog

Moving a project from the hand-written file to fragments:

  1. Release or carry over whatever sits under [Unreleased]. Anything left there is invisible to chlog batch, which only reads fragments -- so either cut a release first, or re-create each pending entry with chlog new.
  2. Add .chlog.yaml at the root, as spelled out in Configuration. This is the step that flips CI; without it the gate keeps asking for a CHANGELOG.md diff.
  3. Leave the released sections of CHANGELOG.md untouched -- chlog appends above them.
  4. Add the header note to CHANGELOG.md saying the file is generated, so the next contributor does not hand-edit it.
  5. Run chlog ai setup to update CLAUDE.md and .github/copilot-instructions.md.
  6. Update CONTRIBUTING.md: add chlog to the prerequisites and replace the "update CHANGELOG.md" step with chlog new.

README Standard

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.

Recommended Sections

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

AI Assistant Instructions

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.

Workflow Integration

Documentation updates must be part of the same commit or PR that introduces the change:

  1. Write the code change.
  2. Record the change in the changelog -- chlog new --kind <Kind> --body "..." when the project uses chlog, or a bullet under [Unreleased] in CHANGELOG.md when it does not. See Deciding Which Mode Applies.
  3. Update README.md -- if the change affects usage, setup, or architecture.
  4. Update .github/copilot-instructions.md -- if the change affects build commands, project structure, or development workflow.
  5. 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.

Automation with AutoBump

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.

References

Clone this wiki locally