Skip to content

The tenzir-changelog harness curates changelog entries, ships release notes, and assembles the public changelog across Tenzir repositories. Use this page as a reference for concepts, configuration, and CLI details. For step-by-step walkthroughs, see the guide for maintaining a changelog.

tenzir-changelog ships as a Python package that requires Python 3.12 or later. Install it with uv (or pip) and verify the console script:

Terminal window
uv add tenzir-changelog
uvx tenzir-changelog --help

Drive the CLI flows from another Python process by importing the Changelog helper:

from pathlib import Path
from tenzir_changelog import Changelog
client = Changelog(root=Path("changelog"))
client.add(
title="API entry",
entry_type="feature",
description="Body text",
co_authors=["claude"],
)
client.show(identifiers=["unreleased"], view="markdown", explicit_links=True)
client.release_notes("v1.0.0", explicit_links=True)
# Get the latest version
version = client.release_version() # Returns "v1.0.0"
bare_version = client.release_version(bare=True) # Returns "1.0.0"
# Publish defaults to latest release
client.release_publish(create_tag=True, assume_yes=True)

For advanced scenarios, reuse tenzir_changelog.create_cli_context and call the helper functions from tenzir_changelog.cli directly.

  • Project – Identifies which documentation stream the changelog belongs to. Every entry and release references the same project string.
  • Entry – A changelog consists of entries. Each entry uses one of four hard-coded types: breaking, feature, bugfix, or change.
  • Unreleased bucket – Pending entries live in unreleased/ until you move them to a release.
  • Release – Versioned milestone under releases/<version>/ containing manifest.yaml, notes.md, and archived entry files in entries/.
  • Configuration file – Settings live in config.yaml by default, or you can store them in package.yaml alongside the changelog/ directory. config.yaml takes precedence when both exist.
  • Export style – Controls whether release notes use a detailed card layout or a compact bullet-list layout. Set export_style: compact in configuration to prefer the bullet-list format without passing --compact each time.
  • Explicit links – Controls whether @mentions and #PR references render as explicit Markdown links. Set explicit_links: true to produce portable output for documentation sites or Markdown renderers that don’t auto-link GitHub references. CLI flags --explicit-links/--no-explicit-links override this setting.
  • Components – You may constrain the optional component field on entries by declaring allowed labels in configuration. Use a dict mapping component names to descriptions. Component filters apply to the CLI table, exports, and validation.
  • Modules – Independent changelog projects discovered via a glob pattern. Each module has its own configuration and versioning. The parent project acts as a workspace that provides discovery, aggregated views, and typically handles distribution of the bundled modules.

A typical project layout looks like this:

  • Directoryproject-root/
    • Directorychangelog/
      • config.yaml
      • Directoryunreleased/
        • add-feature.md
        • fix-bug.md
      • Directoryreleases/
        • Directoryv1.0.0/
          • manifest.yaml
          • notes.md
          • Directoryentries/
            • add-feature.md
            • fix-bug.md

For a package layout (with package.yaml), the structure may look like:

  • Directorymy-package/
    • package.yaml
    • Directorychangelog/
      • Directoryunreleased/
      • Directoryreleases/

Run changelog commands from the project root or the changelog/ directory:

Terminal window
uvx tenzir-changelog [command] [options]

All commands accept --config to point at an explicit configuration file (YAML format, defaulting to config.yaml) and --root to operate on another repository. When you omit both options, the CLI looks for config.yaml inside the changelog root or, failing that, for a package.yaml one directory above the changelog folder. The CLI also automatically uses a changelog/ subdirectory as the project root when running from a parent directory that contains one. This package mode lets you run commands from either the package root or the changelog/ directory without repeating --root.

Display changelog entries in multiple views.

tenzir-changelog show [identifiers...] [options]
OptionDescription
identifiersRow numbers, entry IDs, release versions, or - (optional)
-c/--cardShow detailed cards for matching entries
-m/--markdownExport as Markdown
-j/--jsonExport as JSON
--compactUse compact bullet-list layout
--no-compactUse detailed card layout
--no-emojiRemove type emoji from output
--explicit-linksRender @mentions and PR refs as explicit Markdown links
--project <id>Filter to specific project (repeatable)
--component <label>Filter to specific component (repeatable)
--bannerDisplay project banner above table output

The table view (default) lists entries with ID, title, type, project, PRs, and authors. Row numbers count backward from the newest entry, so #1 always targets the latest change.

Create a new changelog entry in unreleased/.

tenzir-changelog add [options]
OptionDescription
--title <text>Entry title
--type <type>breaking, feature, bugfix, or change
--description <text>Entry body
--author <name>Contributor name (repeatable)
--co-author <name>Additional contributor (repeatable)
--pr <number>Pull request number (repeatable)
--component <label>Component label (repeatable)
--webOpen prefilled GitHub file creation URL

The command prompts for any information you do not pass explicitly. The first invocation scaffolds the project automatically, creating a changelog/ subdirectory with config.yaml and unreleased/. When you provide an explicit --root flag, the CLI uses that directory directly instead of creating a subdirectory. The CLI names entry files using the slugified title (e.g., my-feature.md).

By default, the CLI infers the primary author from environment variables (TENZIR_CHANGELOG_AUTHOR, GH_USERNAME) or the GitHub CLI (gh api user). Using --author overrides this inference entirely. The --co-author option adds to the inferred or explicit author list without replacing it, making it ideal for AI-assisted development, pair programming, or collaborative contributions. Duplicates are removed automatically while preserving order.

Stage a release under releases/<version>/.

tenzir-changelog release create [version] [options]
OptionDescription
versionRelease version (e.g., v1.0.0)
--patchBump patch version from latest release
--minorBump minor version from latest release
--majorBump major version from latest release
--yesCommit changes (default is dry run)
--title <text>Custom title for release heading
--intro <text>Inline intro text (mutually exclusive with --intro-file)
--intro-file <path>Path to intro file (mutually exclusive with --intro)
--compactUse bullet-list layout for notes.md
--explicit-linksRender @mentions and PR refs as explicit Markdown links
--date <YYYY-MM-DD>Override release date

The command renders notes.md, updates manifest.yaml, and moves entry files into entries/. It performs a dry run by default. When the release already exists, the CLI appends additional unreleased entries.

Print the latest released version.

tenzir-changelog release version [options]
OptionDescription
--barePrint version without v prefix

Use this command in scripts to query the current version without parsing output from other commands:

Terminal window
# Get the latest version
tenzir-changelog release version
# Output: v1.2.0
# Get version without prefix (for semver tools)
tenzir-changelog release version --bare
# Output: 1.2.0
# Use in commit messages
git commit -m "Release $(tenzir-changelog release version)"

Re-export release notes without modifying files.

tenzir-changelog release notes <identifier> [options]
OptionDescription
identifierRelease version or - for unreleased
-mExport as Markdown (default)
-jExport as JSON
--compactUse bullet-list layout
--no-emojiDrop type icons
--explicit-linksRender @mentions and PR refs as explicit Markdown links

Use --explicit-links when rendering Markdown outside of GitHub. By default, the output uses @username and #123 references that GitHub auto-links. With --explicit-links, these become explicit Markdown links like [@username](https://github.com/username) and [#123](https://github.com/owner/repo/pull/123), making the output portable to documentation sites, blogs, or other Markdown renderers.

Publish a release to GitHub via gh.

tenzir-changelog release publish [version] [options]
OptionDescription
versionRelease version (defaults to latest)
--yesSkip confirmation prompts
--draftMark as draft
--prereleaseMark as prerelease
--no-latestPrevent GitHub from marking as latest release
--tagCreate and push annotated Git tag
--commitCommit staged changes before tagging (requires --tag)
--commit-messageCustom commit message (default: Release {version})

The command reads project metadata from config.yaml or package.yaml for the repository slug and uses notes.md as the release body. Projects without a repository field cannot publish—this is intentional for changelogs that track changes without producing GitHub releases (e.g., modules in a workspace).

The release publish command executes the following steps:

  1. Validate configuration: Checks that the repository field is set in config.yaml or package.yaml. This field determines the GitHub repository to publish to (e.g., tenzir/tenzir).

  2. Check for gh CLI: Verifies that the GitHub CLI is installed and available in PATH. The command uses gh for all GitHub operations.

  3. Find release manifest: Locates the release manifest at releases/<version>/manifest.yaml. Fails if the specified version doesn’t exist.

  4. Verify release notes: Checks that releases/<version>/notes.md exists and is non-empty. If missing, prompts you to run release create first.

  5. Commit staged changes (if --commit): Creates a git commit with all staged changes. Requires --tag to be set. Uses the commit message from --commit-message, the release.commit_message config field, or defaults to Release {version}.

  6. Create and push git tag (if --tag): Creates an annotated git tag named after the version with message Release {version}. If the tag already exists, skips creation but continues. Pushes the current branch to its upstream remote, then pushes the tag to the remote matching the configured repository.

  7. Check for existing GitHub release: Queries GitHub to determine if a release with this version already exists.

  8. Create or update GitHub release:

    • If the release exists, runs gh release edit to update the release notes and title.
    • If the release doesn’t exist, runs gh release create with the version tag, release notes from notes.md, and optional --draft, --prerelease, or --latest=false flags.
  9. Confirm and execute: Unless --yes is provided, prompts for confirmation before running the gh command. Shows the exact action (gh release create or gh release edit) that will run.

A full release workflow with commit and tag:

Terminal window
# 1. Create the release (generates manifest.yaml and notes.md)
tenzir-changelog release create --minor --yes
# 2. Review the generated notes
tenzir-changelog release notes
# 3. Stage release files
git add changelog/releases/
# 4. Publish with commit and tag (defaults to latest release)
tenzir-changelog release publish --commit --tag --yes

Alternatively, commit manually to customize the message using release version:

Terminal window
# 3. Stage and commit with custom message
git add changelog/releases/
git commit -m "Release $(tenzir-changelog release version)"
# 4. Publish (defaults to latest release)
tenzir-changelog release publish --tag --yes

Both workflows are version-agnostic after step 1. The release publish command defaults to the latest release when no version is specified.

Run structural checks across entry files, release manifests, and exported documentation.

tenzir-changelog validate

The validator reports missing metadata, unused entries, duplicate entry IDs, and configuration drift across repositories. When modules are configured, validation runs against the parent project and all discovered modules. Issues from modules are prefixed with the module ID.

List discovered modules when a modules glob pattern is configured.

tenzir-changelog modules

The command displays a table with module ID, name, relative path, and count of unreleased entries. Use this to discover module paths for --root operations.

Configuration settings live in config.yaml by default, or you can store them in package.yaml alongside the changelog/ directory. config.yaml takes precedence when both exist.

Configuration fields:

FieldDescription
idCanonical project slug written into entry frontmatter (required)
nameHuman-friendly label surfaced in release titles and CLI output
descriptionOptional project description included in release manifests
repositoryGitHub slug (e.g., owner/repo) required by release publish
export_styleDefault layout: compact (bullet-list) or omit for detailed cards
explicit_linksRender @mentions and #PR references as explicit Markdown links (boolean)
omit_prSuppress PR numbers in entries (boolean, default false)
omit_authorSuppress author attribution in entries (boolean, default false)
componentsOptional dict mapping component names to descriptions
modulesOptional glob pattern for discovering nested changelog projects
releaseRelease settings block (see below)

Example:

id: tenzir-core
name: Tenzir Core
description: Core pipeline engine
repository: tenzir/tenzir
export_style: compact
explicit_links: true
components:
cli: Command-line interface and user commands
engine: Core pipeline engine internals
operators: Built-in pipeline operators
release:
commit_message: "Release {version}"

The release block supports:

FieldDefaultDescription
commit_messageRelease {version}Template for --commit flag; {version} expands

The omit_pr and omit_author options suppress PR numbers and author attribution in generated entries. When enabled, the CLI skips auto-detection and ignores any --pr, --author, or --co-author flags (with a warning). Use these options for projects that don’t use GitHub pull requests or prefer anonymous changelog entries.

The first invocation of tenzir-changelog add scaffolds a changelog/ subdirectory with config.yaml, inferring defaults from the parent directory name. When you provide an explicit --root flag, the CLI uses that directory directly. Projects with package.yaml next to changelog/ reuse the package id and name automatically.

Entry files live in unreleased/ or releases/<version>/entries/ as Markdown files with YAML frontmatter. The CLI names entry files using the slugified title (e.g., my-feature.md, fix-bug.md).

Example entry:

---
title: Add pipeline builder
type: feature
author: alice
created: 2025-10-16T14:30:00Z
pr: 101
component: cli
---
Introduces the new pipeline builder UI with drag-and-drop support.

You can use either singular (author, pr, component) or plural (authors, prs, components) keys. The singular form is shorthand for single values and is normalized to the plural form internally.

Frontmatter fields:

FieldTypeRequiredDescription
titlestringyesEntry title
typestringyesbreaking, feature, bugfix, or change
author / authorslist[string]noContributor names (singular or plural form)
createdstringyesCreation datetime in ISO 8601 UTC format
pr / prslist[int]noPull request numbers (singular or plural form)
component / componentslist[string]noLabels matching configured components

Release manifests live at releases/<version>/manifest.yaml and record metadata about a release.

Example manifest:

created: 2025-10-18
title: Big Release
intro: |
Welcome to version 1.0.0!
This release includes significant performance improvements.

Manifest fields:

FieldTypeRequiredDescription
createdstringyesRelease date in YYYY-MM-DD format
titlestringnoCustom title for the release heading
introstringnoIntroductory content (supports Markdown)
modulesdictnoModule versions at release time (auto-populated)

The modules field is automatically populated when creating a release for a parent project with configured modules. It maps module IDs to their latest versions, enabling incremental module summaries in subsequent releases.

Example manifest with modules:

created: 2025-10-18
title: Marketplace v1.0.0
intro: |
Initial release with all plugins.
modules:
git: v1.0.0
docs: v1.1.0
changelog: v1.2.0

The CLI generates notes.md with an H1 heading followed by the intro and grouped entry sections. The heading format depends on whether a custom title is set:

  • With custom title: # {title} (e.g., # Big Release)
  • Without title: # {name} {version} (e.g., # My Project v1.0.0)

Intro content comes from either --intro (inline text) or --intro-file (file path) when creating a release.

JSON exports (-j flag) return structured objects for programmatic consumption. Entries include prs and authors fields as structured objects with URLs.

Each PR is an object with number and optional url:

{
"prs": [
{"number": 123, "url": "https://github.com/owner/repo/pull/123"},
{"number": 456, "url": "https://github.com/owner/repo/pull/456"}
]
}

The url field is included when the project’s repository setting is configured. Without a repository, PRs contain only the number field.

Each author is an object with either handle + url (for GitHub handles) or name (for full names):

{
"authors": [
{"handle": "alice", "url": "https://github.com/alice"},
{"name": "Bob Smith"}
]
}

GitHub handles (names without spaces) include a url field linking to the user’s profile. Full names (containing spaces) include only the name field.

The CLI recognizes these environment variables:

VariableDescription
GITHUB_TOKENGitHub token with repo scope for private repos

Modules enable monorepo support by discovering nested changelog projects via a glob pattern. Each module is a fully independent project with its own configuration, versioning, and release cycle. The parent project serves as a workspace that provides discovery and aggregated views.

Add a modules field to your parent project’s config.yaml:

id: library
name: Tenzir Library
modules: "../packages/*/changelog"

The glob pattern resolves relative to the changelog root directory. Each matched directory must contain a valid config.yaml with its own id and name:

id: amazon_vpc_flow
name: Amazon VPC Flow Package

When a parent project defines modules, it typically acts as a workspace coordinator rather than a versioned project itself. The parent provides:

  • Discovery: Automatically finds modules matching the glob pattern
  • Aggregation: Shows entries from all modules in unified views
  • Validation: Checks module ID uniqueness and configuration validity

The parent may or may not have its own releases. A parent without releases exists purely for coordination.

When modules is configured, show includes entries from all modules. The table displays a Project column showing which project (parent or module) each entry belongs to:

Terminal window
# Show all entries (parent + modules)
tenzir-changelog show
# Show only parent entries
tenzir-changelog show --project parent

When creating a release for a parent project, release create automatically appends a summary of module changes to the release notes. Each module with new entries since the previous parent release gets a section showing:

  • The module name and version as a heading
  • A compact bullet list of entries with emoji prefix, title, and byline
---
## Git Plugin v1.1.0
- 🚀 Add commit message templates — *@alice*
- 🐞 Fix branch detection — *@bob and @claude*
## Docs Plugin v1.2.0
- 🔧 Improve search indexing — *@alice*

The release manifest records each module’s version at release time in the modules field. Subsequent releases compare against this baseline and only include entries from module releases newer than the recorded version. This prevents duplicate entries across parent releases.

The release notes command also renders module summaries when viewing a specific release, using the same incremental logic based on the manifest’s recorded module versions.

Each module is a standalone changelog project. Use --root to operate on a specific module:

Terminal window
# Add entry to a module
tenzir-changelog --root ../packages/amazon_vpc_flow/changelog add --title "Add parser"
# Create a release for a module
tenzir-changelog --root ../packages/amazon_vpc_flow/changelog release create v1.0.0

The modules command lists discovered modules with their paths for convenient copy-paste into --root flags.

Each module maintains its own version history and releases on its own schedule. There is no coordinated release mechanism—modules evolve independently.

Typically, modules do not publish GitHub releases themselves. Instead, the parent workspace handles distribution (e.g., bundling modules into a product release or publishing to a package registry). Modules that need to publish standalone GitHub releases can add a repository field to their configuration.

When modules is configured, tenzir-changelog validate checks:

  • Module ID uniqueness across all discovered modules
  • Valid configuration in each module directory
  • Validation errors – Run tenzir-changelog validate to identify missing metadata, unused entries, or duplicate IDs.
  • Component mismatch – When components is configured, ensure every entry either omits component or uses an allowed label.
  • Configuration not found – Ensure config.yaml exists in the changelog root or package.yaml sits next to the changelog/ directory. Run tenzir-changelog add once to scaffold the configuration.
  • Version bump fails – Bump flags read the latest release manifest on disk. Create an initial release with an explicit version before using --patch/--minor/--major.

Last updated: