# moneta

A changelog built from git history.

Change entries are Markdown files under `.changes`. Which release each one
belongs to is **the first tag whose tree contains it** — so a release is a tag
and nothing else. No commit bumps a version, nothing is moved or deleted at
release time, and the changelog follows from the history.

```console
$ moneta new -k added -m "Support nested .changes directories" -i 42
.changes/20260923T142530Z-added-support-nested-changes-directories.md

$ moneta next
v0.2.0

$ git tag v0.2.0          # that is the entire release ceremony
```

```{toctree}
:hidden:

cli
sphinx
changelog
```

## The rule

```text
files(T)    = the fragment keys under .changes in tag T's tree
baseline(T) = the union of files(A) over every ancestor tag A of T
entries(T)  = files(T) minus baseline(T)
```

Three choices in there are load-bearing, and each rules out a wrong answer:

Tree presence, not "the commit that added the file"
: After a rebase, a squash or a cherry-pick, *which commit introduced this*
  has several defensible answers. *Was it in the release* has exactly one.

A union over every ancestor, not a diff against the newest
: Picking one predecessor goes wrong the first time a branch is merged.
  Nothing in attribution depends on semver ordering, so two tags on one
  commit, a mistyped tag and tags on divergent lineages cannot produce a wrong
  answer.

Keys, not paths
: A fragment's key is its filename stem, or an explicit `id:`. Moving one into
  a subdirectory is a delete and an add under path comparison, which would
  silently re-credit the entry to a later release.

## Editing an entry after it has shipped

Attribution is fixed by the tags. What an entry *says* is read from the working
tree, so fixing a typo in an entry from two releases ago reaches the next docs
build without moving it. Deleting the fragment falls back to the text its tag
holds, so tidying `.changes` never erases history.

The one edit that does move an entry is changing its key after it has shipped:
the old key stays attributed to its release while the new one matches nothing
and lands in Unreleased, so the entry appears twice. `moneta check` fails on it
and names both keys. Set `id:` *before* a release if a rename is coming.

## Shallow clones

moneta refuses to run in a shallow clone, and the refusal is the feature. A
grafted history does not fail an ancestry question, it answers it wrongly, so
the result would be a changelog quietly missing whole releases — published to
a docs site and to a release page with nothing anywhere saying so.

In GitLab CI, set `GIT_DEPTH: 0` on any job that runs moneta.

`--allow-shallow` degrades instead of refusing. It sets `"truncated": true` in
the JSON and puts a warning at the top of every rendered format, so the result
still says what it is.
