Skip to main content

Durable release-state storage and CLI for coding workflows

Project description

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, audit 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.

Releaseledger reuses ledgercore for 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.

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 event to events.jsonl and rebuilds the JSON indexes.

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

releaseledger init

releaseledger release create 1.2.0 \
  --title "Release 1.2.0" \
  --boundary-ref tl:task-0105 \
  --source-ref tl:task-0103

releaseledger entry add 1.2.0 \
  --kind added \
  --summary "Added release bundle storage" \
  --status accepted \
  --source-ref tl:task-0103

releaseledger entry lint 1.2.0 --strict

releaseledger changelog 1.2.0 \
  --target-changelog CHANGELOG.md \
  --release-date 2026-06-13

releaseledger build 1.2.0 \
  --dry-run \
  --strict \
  --target-file CHANGELOG.md

releaseledger build 1.2.0 \
  --release-date 2026-06-13 \
  --strict \
  --target-file CHANGELOG.md

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 JSONL audit row written after each mutation.
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, and yanked. 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]
releaseledger release tag VERSION [release metadata options]
releaseledger release finalize VERSION [--released-at YYYY-MM-DD]
                                       [--changelog-file PATH]
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]
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 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 storage where
releaseledger config show
releaseledger config set releaseledger_dir PATH [--external-dir]

Root options:

releaseledger --cwd 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.

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" },
]

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

Project details


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.1.0.tar.gz (81.6 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.1.0-py3-none-any.whl (64.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for releaseledger-0.1.0.tar.gz
Algorithm Hash digest
SHA256 48dc2f3a5eaa1432810df0e7c2fad256980c7a7ba98e47d587df2e6ea9ad9c68
MD5 4fceca1182aaabdcb65842ef70012b09
BLAKE2b-256 07c5a75a7d091313285e6fa8cb8d0d9ed18af86019b0565295e9de4f68a5c5a8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: releaseledger-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 64.3 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e554be6229385aa982cd440dc062585254101da2d2b600f33d2202c14ae2b2a0
MD5 53019f1e084a00aa38463901f47ceb49
BLAKE2b-256 118a2c233a0e146a9d315e7158e4010a6fd0641d320e10237f0e4412bc112079

See more details on using hashes here.

Supported by

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