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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48dc2f3a5eaa1432810df0e7c2fad256980c7a7ba98e47d587df2e6ea9ad9c68
|
|
| MD5 |
4fceca1182aaabdcb65842ef70012b09
|
|
| BLAKE2b-256 |
07c5a75a7d091313285e6fa8cb8d0d9ed18af86019b0565295e9de4f68a5c5a8
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e554be6229385aa982cd440dc062585254101da2d2b600f33d2202c14ae2b2a0
|
|
| MD5 |
53019f1e084a00aa38463901f47ceb49
|
|
| BLAKE2b-256 |
118a2c233a0e146a9d315e7158e4010a6fd0641d320e10237f0e4412bc112079
|