Commands#
moneta --help lists them all; this page is the part worth knowing before
reading the flags.
Writing an entry#
$ moneta new -k added -m "Support nested .changes directories" -i 42
$ moneta create
new takes every field as a flag and is what a script calls. create asks for
the kind and the scopes — both come from lists the project already defines —
and then opens the fragment itself in $EDITOR, frontmatter included, so what
you commit is what you looked at. Nothing is written until the editor exits
with an entry in it.
A fragment is Markdown with YAML frontmatter:
---
kind: added
scope: [parser, config]
issues: [42]
breaking: false
---
Support nested `.changes` directories.
scope: takes a string or a list. issues: and merge-requests: take numbers
or strings, so a project that writes GH-3 needs no migration. breaking: true
forces a major bump whatever the kind’s own bump is.
The filename carries a UTC timestamp, which makes it conflict-free — two merge requests collide only by landing in the same second with the same description — and makes lexicographic order chronological, which is the ordering within a release. There is no index file, so git has nothing to conflict on.
filename: in .moneta.yaml decides the shape, over {{ .Timestamp }},
{{ .Kind }}, {{ .Scope }} and {{ .Slug }}.
Rendering#
$ moneta render # Markdown, every version
$ moneta render --format rst -o docs/changes.rst
$ moneta render --format json # the contract other tools read
$ moneta render --version current --format md # this tag's release notes
--version current is the release path: inside a tag pipeline HEAD is detached
at the tag being built, and what you want is that tag’s notes, not an Unreleased
section that is empty because everything just shipped.
Release headings link to whatever the project publishes per version, when
releases.url says where:
releases:
url: "https://gitlab.com/terryp/moneta/-/releases/{{ .Tag }}"
Only the version is the link — the date and the [YANKED] marker stay outside
it — and Unreleased is never linked, because it has no tag to build a URL from.
With no releases.url the headings are plain, which is what they were before.
--content decides where entry text comes from, never which version an entry
is in:
|
the file as it is now, so a just-written or just-fixed entry shows up |
|
the committed text |
|
every entry as its own release shipped it |
Under worktree and head, an entry whose key is gone from the working tree
falls back to the blob at the tag that attributed it. Deleting a fragment tidies
.changes without erasing history from the changelog.
Releasing#
$ moneta next # the version to cut
$ moneta next --bump-only # major | minor | patch | none
$ moneta next --tag --push
The bump comes from the kinds waiting to be released: a breaking change moves
the major, a feature the minor, a fix the patch. Below 1.0 the default policy
softens that — a breaking change is a minor bump, anything else a patch — until
pre-1.0.policy: strict says otherwise.
moneta only ever suggests. A tag someone typed is the truth, and --tag
refuses to add a second tag to a commit that already has one unless --force
says so.
Checking#
$ moneta check # parse and validate every fragment
$ moneta check --require-fragment # also: changed code, added no entry
--require-fragment is a gate for merge requests. It has no skip trailer and no
exempt path list on purpose: an escape hatch inside the tool is one nobody sees,
where bypassing a red pipeline is visible and deliberate.
check also catches the one edit that silently moves an entry — a fragment
whose key changed after it shipped — and names both keys.
Looking#
$ moneta list
VERSION DATE ENTRIES FROM
Unreleased 2 v0.1.0
v0.1.0 2026-09-23 7
The FROM column names the releases whose contents formed that version’s
baseline, which is the quickest way to see why an entry landed where it did.