Skip to main content

PyPI - Version PyPI - Python Version PyPI - Downloads codecov

releaseledger

Project-local release management for coding workflows.

releaseledger is a standalone release-state ledger for Python projects and other source repositories. It records releases, release-note entries, operation events, and JSON indexes in a deterministic file layout. It can also render reviewable changelog context and write final CHANGELOG.md sections from releaseledger entries.

The current CLI uses the canonical Ledgercore project layout. Use --root to select a project, commands/help for the metadata-backed command inventory, and --json for the stable ledgerwerk.cli.v1 envelope:

releaseledger --root PATH status
releaseledger commands
releaseledger --root PATH --json doctor

The canonical CLI uses --root and the explicit copy-first migrate plan/apply/recover/cleanup lifecycle. Legacy --cwd and storage migrate remain available only as deprecated compatibility aliases with structured warnings.

typed file-storage primitives. It does not import taskledger, inspect .taskledger/, or validate task state. Cross-ledger provenance is represented only as explicit refs such as tl:task-0103.

Git-first. Releaseledger uses git commit ranges as the canonical evidence of shipped changes. Git tags and commit ranges define the shipped change set. Taskledger, issue trackers, and PR descriptions are optional provenance context. Release notes are generated from the commits reachable from the release target and absent from the previous release target:

releaseledger release update 1.2.0 --git-base v1.1.0 --git-head HEAD
releaseledger git scaffold 1.2.0 --output entries.yaml
releaseledger release check 1.2.0 --strict --target-file CHANGELOG.md

What releaseledger stores

After releaseledger init, a project has a .releaseledger.toml config file and a state directory, usually .releaseledger/:

.releaseledger/
  ledgers/
    main/
      releases/
        1.2.0/
          release.md
          entries/
            entry-0001.md
      events/
        events.jsonl
      indexes/
        releases.json
        entries.json

Release records and entries are Markdown files with YAML front matter. Every mutation appends an operation event to events.jsonl and rebuilds the JSON indexes. Events omit wall-clock timestamps and before/after deltas; git history provides chronology and record revisions validate file changes.

Install

python -m pip install releaseledger

For local development:

python -m pip install -e ".[dev]"

The package exposes the console command releaseledger and supports python -m releaseledger.

Quickstart

# 1. Initialize.
releaseledger init

# 2. Create the release and attach the git snapshot once.
releaseledger release create 1.2.0 \
  --previous 1.1.0 \
  --released-at 2026-06-14

releaseledger release update 1.2.0 \
  --git-base v1.1.0 \
  --git-head HEAD

# 3. Export deterministic evidence, initialize the audit, and scaffold entries.
releaseledger git evidence 1.2.0 --output-dir /tmp/1.2.0-evidence
releaseledger audit init 1.2.0
releaseledger audit show 1.2.0 --format yaml --output /tmp/1.2.0-audit.yaml
releaseledger git scaffold 1.2.0 --output /tmp/1.2.0-git-entries.yaml

# 4. Curate audit annotations, then validate the evidence phase.
releaseledger audit apply 1.2.0 \
  --file /tmp/1.2.0-audit-decisions.yaml \
  --dry-run
releaseledger audit apply 1.2.0 \
  --file /tmp/1.2.0-audit-decisions.yaml
releaseledger audit validate 1.2.0 --phase evidence --strict

# 5. Edit the entry scaffold summaries, then validate and write atomically.
releaseledger entry add-many 1.2.0 \
  --file /tmp/1.2.0-git-entries.yaml \
  --dry-run \
  --strict \
  --guard-commit-subjects
releaseledger entry add-many 1.2.0 \
  --file /tmp/1.2.0-git-entries.yaml \
  --strict \
  --guard-commit-subjects \
  --sync-audit
releaseledger audit validate 1.2.0 --phase complete --strict --include-internal

# 6. Run the final read-only gate, then finalize and build.
releaseledger release check 1.2.0 --strict --target-file CHANGELOG.md
releaseledger release finalize 1.2.0 --released-at 2026-06-14
releaseledger build 1.2.0 --strict --target-file CHANGELOG.md

# Or rebuild the COMPLETE changelog from ledger state:
releaseledger build --dry-run --strict --target-file CHANGELOG.md
releaseledger build --target-file CHANGELOG.md

Taskledger refs (tl:task-0103) and PR refs (github:pr-42) are optional provenance — use them to enrich entries, but git commits are the coverage source.

changelog produces agent-facing context for review or drafting. build renders and inserts the final changelog section.

Core concepts

Concept Meaning
Release A versioned release record with status, optional previous version, source boundary, and changelog target.
Entry One release-note item attached to a release. Entries are grouped by kind for changelog output.
Event Append-only operation marker with affected record revisions.
Versioning Per-record metadata whose revision increases exactly once when a release or entry file changes.
Index Deterministic JSON summary rebuilt after mutations for fast inspection.
Ledger ref Branch-scoped namespace, defaulting to main.
Global source ref External provenance token such as tl:task-0103; releaseledger records it but does not resolve it.

Release statuses are planned, draft, candidate, released, yanked, and canceled (never shipped; excluded from previous-version inference and not built into public changelogs by default). Entry statuses are draft, accepted, and rejected. Builds include accepted entries by default.

Entry kinds are added, changed, fixed, removed, deprecated, security, docs, quality, and internal. documentation and doc are accepted aliases for docs.

Commands

releaseledger init [--releaseledger-dir PATH] [--project-name NAME]
                  [--external-dir] [--force]

releaseledger release create VERSION [--title TEXT] [--status STATUS]
                                     [--previous VERSION] [--note TEXT]
                                     [--changelog-file PATH]
                                     [--released-at YYYY-MM-DD]
                                     [--boundary-ref REF]
                                     [--source-ref REF]...
                                     [--source-count N]
releaseledger release update VERSION [same metadata options]
                                     [--clear-previous]
                                     [--clear-changelog-file]
                                     [--clear-boundary-ref]
                                     [--clear-source-refs]
                                     [--clear-source-count]
                                     [--clear-released-at] [--force]
releaseledger release tag VERSION [release metadata options]
releaseledger release finalize VERSION [--released-at YYYY-MM-DD]
                                       [--changelog-file PATH]
releaseledger release prepare VERSION [--previous VERSION]
                                      [--released-at YYYY-MM-DD]
                                      [--git-base REF] [--git-head REF]
                                      [--output-dir PATH]
releaseledger release check VERSION [--target-file PATH] [--strict]
releaseledger release cancel VERSION [--reason TEXT]
                                    [--superseded-by VERSION]
                                    [--force-released-unshipped]
                                    [--target-file PATH]
                                    [--remove-changelog-section]
                                    [--ignore-missing]
releaseledger release rename OLD_VERSION NEW_VERSION [--previous VERSION]
                                                      [--title TEXT]
                                                      [--released-at YYYY-MM-DD]
                                                      [--force-released-unshipped]
                                                      [--rewrite-successors]
                                                      [--target-file PATH]
                                                      [--rename-changelog-section]
                                                      [--replace-existing-section]
releaseledger release chain check
releaseledger release chain repair [--dry-run] [--apply]
releaseledger release list
releaseledger release show VERSION

releaseledger entry add VERSION --kind KIND --summary TEXT [--body TEXT]
                               [--status STATUS] [--audience TEXT]
                               [--scope SCOPE]... [--source-ref REF]...
                               [--path PATH]... [--issue REF]... [--pr REF]...
                               [--breaking] [--internal] [--dry-run]
releaseledger entry add-many VERSION --file FILE [--dry-run] [--strict]
                                    [--guard-commit-subjects]
                                    [--sync-audit]
releaseledger entry update VERSION ENTRY_ID [entry metadata options]
releaseledger entry show VERSION ENTRY_ID
releaseledger entry import VERSION --file FILE [--replace]
                                   [--source-ledger LEDGER]
releaseledger entry list VERSION
releaseledger entry lint VERSION [--strict] [--include-status STATUS]...
releaseledger entry prompt VERSION [--source-ref REF]...
                                   [--context-file FILE]
                                   [--format markdown|json]
                                   [--output PATH]

releaseledger changelog preview VERSION [--format markdown|json] [--output PATH]
                                [--include-internal]
                                [--target-changelog PATH]
                                [--release-date YYYY-MM-DD]
                                [--include-sources]
                                [--include-status STATUS]... [--lint]

releaseledger build VERSION [--target-file PATH]
                            [--release-date YYYY-MM-DD]
                            [--unreleased]
                            [--include-internal]
                            [--template NAME]
                            [--dry-run]
                            [--replace-existing]
                            [--format markdown|json]
                            [--include-status STATUS]...
                            [--strict]
                            [--allow-empty]
releaseledger build [VERSION] [--all] [--target-file PATH]
                            [--include-release-status STATUS]...
                            [--preserve-unreleased|--no-preserve-unreleased]
                            [--include-internal]
                            [--include-status STATUS]... [--strict]
                            [--dry-run] [--allow-empty]
releaseledger review VERSION [--include-internal]
                       [--include-status STATUS]...
                       [--target-file PATH] [--strict]
                       [--git] [--git-base REF] [--git-head REF]
                       [--require-audit-sheet]
releaseledger release check VERSION [--target-file PATH] [--strict]
releaseledger audit init VERSION [--base REF] [--head REF] [--overwrite]
releaseledger audit show VERSION [--format markdown|json|yaml] [--output PATH]
releaseledger audit apply VERSION --file PATH [--dry-run]
releaseledger audit refresh VERSION [--base REF] [--head REF] [--allow-remove]
releaseledger audit update VERSION --file PATH
releaseledger audit validate VERSION [--phase evidence|complete]
                                  [--strict] [--include-internal]
releaseledger audit sync VERSION

releaseledger git range VERSION [--base REF] [--head REF]
                      [--include-merges never|always|nontrivial]
releaseledger git range next --base REF [--head REF]
releaseledger git scaffold VERSION [--base REF] [--head REF]
                       [--status draft] --output PATH
releaseledger git import VERSION [--base REF] [--head REF]
                      [--status draft] --output PATH
releaseledger git import next --base REF [--head REF] --output PATH
releaseledger git evidence VERSION [--base REF] [--head REF]
                       [--output-dir PATH]

releaseledger branch status
releaseledger branch start BRANCH --parent PARENT
releaseledger branch merge BRANCH --into TARGET --release VERSION

releaseledger changelog-section remove-section VERSION --target-file PATH
                                              [--ignore-missing] [--dry-run]
releaseledger changelog-section rename-section OLD_VERSION NEW_VERSION
                                              --target-file PATH
                                              [--ignore-missing]
                                              [--replace-existing] [--dry-run]

releaseledger storage where
releaseledger config show
releaseledger config set releaseledger_dir PATH [--external-dir]

Root options:

releaseledger --root PATH ...
releaseledger --json ...
releaseledger --version

Batch entries

entry add-many reads YAML with a top-level entries list:

entries:
  - kind: added
    summary: Added release bundle storage
    body: >-
      The storage layer now writes release records, entries, events, and indexes.
    status: accepted
    audience: developer
    scopes: [storage]
    source_refs: [tl:task-0103]
    paths:
      - releaseledger/storage/store.py
    issues: []
    prs: []
    breaking: false
    internal: false

Run a dry run before writing:

releaseledger entry add-many 1.2.0 --file /tmp/1.2.0-entries.yaml --dry-run
releaseledger entry add-many 1.2.0 --file /tmp/1.2.0-entries.yaml

Changelog generation

There are two changelog commands:

releaseledger changelog builds review context. It is useful for coding agents or humans who need to inspect release metadata, included entries, target file guidance, and lint findings before writing final prose. Add --include-sources when the Markdown output should show provenance refs.

releaseledger build renders the final section from [changelog] config and inserts it into the target file. It can run in --dry-run mode, replace an existing release section with --replace-existing, or render an unreleased date with --unreleased. Use --template NAME to select a named changelog template profile.

Use the conventional full rebuild to regenerate the whole target file from ledger state: releaseledger build with no VERSION (or releaseledger build --all) rewrites the entire document newest-first, preserves the ## [Unreleased] body by default, excludes internal entries and non-released releases by default, and regenerates the link-reference block. releaseledger build VERSION keeps the single-section insert/replace behavior. Use --include-release-status to include candidate/planned sections explicitly, and --include-internal only for internal release notes.

Default .releaseledger.toml changelog template:

[changelog]
output = "CHANGELOG.md"
trim = true
render_always = false
header = ""
body = """
## {% if release.date %}[{{ release.version }}] - {{ release.date }}{% else %}[{{ release.version }}] - Unreleased{% endif %}

{% for group in groups %}
### {{ group.title }}
{% for entry in group.entries %}
- {% if entry.breaking %}**BREAKING:** {% endif %}{{ entry.summary }}
{% endfor %}

{% endfor %}
"""
footer = "<!-- generated by releaseledger -->"
postprocessors = []

Templates run in a sandboxed Jinja2 environment and may access project, release, entries, groups, and releases. Postprocessors are literal string replacements:

postprocessors = [
  { pattern = "releaseledger", replace = "Releaseledger" },
]

Release review

releaseledger review VERSION is a read-only coverage report that combines release state, entry coverage, orphan detection, entry lint, and a strict changelog dry-run into one deterministic report. Use it to answer "what did I already add for this release?" without stitching together release show, entry list, entry lint, changelog, and build --dry-run.

releaseledger review 0.5.0
releaseledger --json review 0.5.0
releaseledger review 0.5.0 --include-status accepted --include-status draft
releaseledger review 0.5.0 --strict --target-file CHANGELOG.md

Each expected source ref (release.source_refs plus boundary_ref) is classified as covered, draft_only, rejected_only, internal_only, or missing. Accepted entries with no provenance (empty source_refs, issues, prs, and sources) are reported as orphans. Git hashes remain optional evidence in entry sources; source_refs plus entry status are the canonical change identity.

Before adding a new entry, run releaseledger review VERSION. If the same source_ref is already covered by an accepted entry, update the existing entry instead of adding a duplicate.

Correcting canceled or misnumbered releases

When a recorded release was never actually shipped (no git tag, no package publish) or was recorded under the wrong version number, fix it with the release-correction commands instead of editing .releaseledger/ storage.

# 1. Verify the real shipped baseline first (git tags / package index / user).
git tag --list | sort -V | tail

# 2. Inspect the stored chain and repair a broken backfill.
releaseledger release chain check
releaseledger release chain repair --dry-run
releaseledger release chain repair --apply

# 3. Clear an optional field on a root release (e.g. v0.1.0).
releaseledger release update v0.1.0 --clear-previous

# 4. Rename an unshipped, misnumbered release and its changelog section.
releaseledger release rename v0.4.3 v0.5.0 \
  --previous v0.4.2 \
  --force-released-unshipped \
  --target-file CHANGELOG.md \
  --rename-changelog-section

# 5. Or keep the wrong version as a visible audit tombstone.
releaseledger release cancel v0.4.3 \
  --reason "Never shipped; superseded by v0.5.0" \
  --superseded-by v0.5.0 \
  --force-released-unshipped

Decision tree:

  • Check shipped evidence first (git tags, changelog headings, explicit user statement).
  • If a version was never shipped and the number was wrong, use release rename.
  • If the wrong version should remain as an audit tombstone, use release cancel (sets status canceled; never use yanked for never-shipped releases).
  • When backfilling old releases, always pass --previous explicitly and run release chain check afterwards.
  • Build the changelog from the net shipped baseline, then bump the package version.

Cross-ledger provenance

Releaseledger is intentionally standalone. To link work from another tool, export that tool's evidence and pass it as opaque context:

taskledger task show task-0103 --json > /tmp/task-0103.json

releaseledger entry prompt 1.2.0 \
  --source-ref tl:task-0103 \
  --context-file /tmp/task-0103.json \
  --output /tmp/entry-prompt.md

releaseledger entry add-many 1.2.0 --file /tmp/1.2.0-entries.yaml --dry-run
releaseledger entry add-many 1.2.0 --file /tmp/1.2.0-entries.yaml
releaseledger entry lint 1.2.0 --strict
releaseledger build 1.2.0 --dry-run --strict --target-file CHANGELOG.md

The prompt command tells the drafting agent to use only releaseledger metadata, explicit source refs, and caller-supplied context.

JSON envelopes

Use --json for deterministic machine-readable output.

Success envelope:

{
  "command": "release.tag",
  "events": ["event-0001"],
  "ok": true,
  "result": {
    "events": ["event-0001"],
    "kind": "release",
    "ledger_ref": "main",
    "release": {
      "status": "released",
      "version": "1.2.0"
    }
  },
  "result_type": "release"
}

Error envelope:

{
  "command": "release.tag",
  "error": {
    "code": "USAGE_ERROR",
    "exit_code": 2,
    "message": "Release version already exists: 1.2.0",
    "remediation": ["Run `releaseledger release show 1.2.0`."]
  },
  "ok": false
}

Common error codes are USAGE_ERROR, NOT_FOUND, CONFIG_ERROR, VALIDATION_ERROR, and CONFLICT.

Configuration

Default local state:

# .releaseledger.toml
config_version = 1
releaseledger_dir = ".releaseledger"

ledger_ref = "main"
ledger_parent_ref = ""
ledger_next_entry_number = 1
ledger_branch_guard = "off"

[ledger]
code = "rl"
name = "releaseledger"

[release]
default_changelog = "CHANGELOG.md"
default_status = "planned"
allow_dirty_worktree = true

Projects that keep generated state in a sibling repository can opt in to an external relative path:

releaseledger_dir = "../ledger/release/releaseledger"
releaseledger_dir_policy = "external"

The CLI equivalent is:

releaseledger init \
  --releaseledger-dir ../ledger/release/releaseledger \
  --external-dir

releaseledger config set releaseledger_dir \
  ../ledger/release/releaseledger \
  --external-dir

Relative paths that escape the workspace are rejected unless the external policy is explicit. Absolute paths are accepted for compatibility but are not portable.

Storage diagnostics

Inspect effective paths and layout health without mutating state:

releaseledger storage where
releaseledger --json storage where
releaseledger config show
releaseledger --json config show

Human output from storage where includes workspace, config path, storage path, ledger ref, workspace containment, config source, layout status, and index status.

Python API

The public API is intentionally narrow and re-exported from releaseledger.api:

from releaseledger.api.releases import (
    create_release,
    finalize_release,
    list_release_records,
    show_release,
    tag_release,
    update_release,
)
from releaseledger.api.entries import (
    add_many_release_entries,
    add_release_entry,
    build_entry_prompt,
    import_release_entry_file,
    lint_release_entries,
    list_release_entries,
    show_release_entry,
    update_release_entry,
)
from releaseledger.api.changelog import (
    build_changelog_context,
    build_changelog_file,
    render_changelog_section,
)
from releaseledger.api.config import (
    config_set_releaseledger_dir,
    config_show,
    discover_workspace_root,
    load_project_config,
    load_project_locator,
    render_default_releaseledger_toml,
    require_project,
    storage_where,
)

Service functions return plain dictionaries or strings and raise releaseledger.errors.LaunchError for user-facing failures. They do not print or call typer.Exit.

Development

python -m pip install -e ".[dev]"
pytest -q
ruff check .
mypy releaseledger
python -m build

Build documentation:

python -m pip install -e ".[docs]"
sphinx-build -b html docs docs/_build/html

The project ships py.typed and targets Python 3.10+.

License

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

releaseledger-0.4.0.tar.gz (384.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

releaseledger-0.4.0-py3-none-any.whl (213.2 kB view details)

Uploaded Python 3

File details

Details for the file releaseledger-0.4.0.tar.gz.

File metadata

  • Download URL: releaseledger-0.4.0.tar.gz
  • Upload date:
  • Size: 384.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for releaseledger-0.4.0.tar.gz
Algorithm Hash digest
SHA256 941a4dfae7fa1a549068971b70d47c8679831df28b8746cd916fff3abf2203d4
MD5 ebca1e7311cd32b85788113dc909f0db
BLAKE2b-256 e970bd2189b49b297509420516f7f687a26e04284006fced6bf68e4b4d5b0842

See more details on using hashes here.

File details

Details for the file releaseledger-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: releaseledger-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 213.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for releaseledger-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 88750b25eef32d0ce0d2491617245616119d7bfa473343a1aed1e3a09730f5ca
MD5 537d53c861581864f8011d3856e85684
BLAKE2b-256 34e2fab771704a00856fb20b3a2b0d4ac74c084c42f2dff570be2777e86a5e77

See more details on using hashes here.

Release history Release notifications | RSS feed

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

This release

0.4.0 This release

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page