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:

worktree (default)

the file as it is now, so a just-written or just-fixed entry shows up

head

the committed text

as-published

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.