On this page
Changelog authoring and collection
A repository that keeps released entries in changelog/ writes new entries under
## Unreleased in CHANGELOG.md. The changelog command moves them so no file
passes the repository's length limit. Both modes require --line-limit: the limit
belongs to the repository, not to Stado.
Structural ## Released and ## Unreleased headings must be unique. When both
exist, history must precede unreleased content. Collection requires its Released
history anchor instead of inventing one. Repeated or misordered headings are refused
before output planning, with their observed zero-based line indices. Build submission
also reports ambiguous layout rather than judging only the first section.
Root reads accept LF and CRLF line endings and recognize a following section even without a blank line. Unchanged prefix text and later sections keep their original line endings; generated entry/link content uses LF. Spilled-part collection retains the complete payload after its Unreleased heading, including any later headings.
stado product changelog --spill --line-limit <limit>
stado product changelog --version <version> --line-limit <limit>
stado product changelog --entry <name> --section Added|Changed|Fixed|Removed --file <entry.md> --line-limit <limit>
Spill entries while preparing a release
--spill moves the entries written so far into changelog/unreleased/<B>/part-<N>.md
files, each within the limit and in a folder of their own so the range folder does
not grow, five parts to each numbered folder (parts 1 to 5 in 1/, 6 to 10 in 2/)
so no folder holds more than five files. It leaves their links under ## Unreleased,
ready for the next entry. A linked part kept anywhere else, as an older spill wrote
it (changelog/unreleased/part-<N>.md, or unreleased-part-<N>.md beside the
changelog), is moved into its numbered folder and its link follows it, also when
there is nothing new to spill. A part cut from a ### Added or ### Fixed group
opens with that heading; no entry is split across parts.
The answer is {"changelog", "spilled", "parts", "filed"}: filed names the
linked parts moved into their numbered folders.
Empty grouping headings such as ### Added are an outline, not release content.
Spilling an outline creates no part, and an outline alone does not block build
submission. Ordinary prose remains content; it is not discarded for lacking a bullet.
Part numbering is checked before any part file is written or CHANGELOG.md is
rewritten. Exhausting the supported part-number range is a refusal, not a wrapped
number or a renumbering of existing links. The diagnostic names either the last
linked part and failed increment, or the planned first part and offset whose sum
cannot be represented.
Write one change's entries in a file of its own
A change writes its entries in changelog/unreleased/changes/<B>/change-<words>.md,
in the first numbered folder holding fewer than five files, a file
no other change writes, named by words of the change itself (never a register or
session identifier, which a public file does not carry), holding its entries as
## Unreleased would: - **Subject:** what changed. lines, under ### Added
or ### Fixed when the repository groups them. No link names the file. Two
changes written apart then never edit one file: each change that added its entry
to the shared section, or spilled into the next part-<N>, met every other one
in a merge conflict, and the submit queue refused over a hundred changes for it.
The unlinked-part check passes over these files, and Tama reads them as the
repository's public pages.
--entry <name> --section <kind> --file <entry.md> writes that file: the bullets
<entry.md> holds, under ### <kind>, so the version bump files them with the
release's other entries of that kind. <name> is lowercase letters and digits in
words joined by single dashes. The file is created only where none stands, and a
change of the same name kept in any numbered folder refuses the write, so a
second change of the same name never writes over the first one's entries; a write
that fails part way removes what it wrote. The answer is {"entry", "section", "lines"}.
Its refusals are declared under stado.product.changelog.entry
(stado errors show stado.product.changelog.entry):
entry-name-form(exit 2): the name is not words joined by dashes;entry-exists: a change already keeps its entries at that path;entry-text-absent,entry-text-unread: no file at--file, or one the system will not read as UTF-8;entry-text-form(exit 2): the text is empty, opens with no-bullet, holds a heading, or has a line neither a new bullet nor indented under one;entry-text-too-long: the file would pass--line-limit;entry-unwritten: the system refused to write the file.
Collect a release
--version runs in the version-bump commit. It gathers every spilled part, oldest
first, then every change's own entry file in name order, with what the section
still holds, and groups entries under one copy of each heading. It writes that
release into the newest range file, or into new changelog/<B>/<version>.md parts
when the range has no room, removes the gathered parts and entry files and
empties the section. stado build submit refuses a revision whose tree still
holds a change's entry file, naming each, as it refuses Unreleased content left
in CHANGELOG.md.
Range files are kept five to a numbered folder of changelog/: counted from the
oldest link under ## Released, range P stands in folder (P - 1) / 5 + 1, and
a range a release extends keeps its folder. Before it writes the release, the bump
moves every range an older version wrote straight under changelog/ into its
folder and rewrites its link at once, so no link names a file that moved; one it
cannot move is stado.product.changelog.range-unfiled, naming both paths and what
the system said, and leaves that range and its link as they were.
The answer is {"changelog", "moved", "version", "range_file", "gathered_parts", "filed"}:
filed names the range files moved into their numbered folders.
If all linked parts contain only empty group outlines, collection removes those
parts and clears their links without creating an empty release file. The receipt's
gathered_parts names the consumed paths; there is no range_file in this case.
Following root sections are preserved during both ordinary and empty collection.
Selected history input
Before publishing a nonempty release, collection opens the newest linked history
file once. The same read text supplies its line count and the body used for range
extension. Complete hyphenated version identities are retained; a version that
contains -part- is not mistaken for multipart output.
The file's first line declares its layout: # Changelog <VERSION>,
# Changelog <FIRST> – <LAST>, or
# Changelog <VERSION> (part <N> of <COUNT>). Its filename must agree with that
metadata; part ordinals must be nonzero and no greater than the declared total.
The first release heading must name VERSION, or LAST for a range. Missing or
inconsistent metadata is a refusal, not an excuse to guess a version or discard
the earlier payload. An invalid selected link is not treated as empty history.
Selected targets must be relative paths beneath the declared history directory and regular files. On Unix the final component is opened without following symlinks, and nonblocking opening allows rejecting nonregular inputs such as FIFOs rather than waiting for their producer. This is a lexical path contract, not a claim that every parent directory is free of symlinks. Errors retain the selected directory/file, observed metadata and actual failed operation.
Checkout writer ownership
Both moves hold the same checkout writer from the root read through output writes, gathered-part cleanup and receipt emission. Another Stado changelog invocation for that checkout is refused, not allowed to plan from the first writer's old root. The refusal includes the selected lock path and the existing holder diagnostics: PID, process identity, command and acquisition time. It does not wait, retry or stop the holder.
The lock identity uses the resolved checkout path, while receipts keep the supplied
root's path spelling. Operational metadata lives at .build/changelog/writer.lock
under that checkout and is not changelog content.
New output publication
Both modes preflight every planned new output before publishing any. An existing spill part, release part or renamed range destination is refused with its path and observed file type; an inspection failure keeps the actual filesystem cause. Directories and dangling symlinks are not mistaken for absent files.
Publication also refuses a destination created after preflight. It writes and syncs an exclusively owned temporary file in the same directory, then publishes by hard link without replacing an existing path. A filesystem that cannot provide that operation refuses with its own error; there is no destructive fallback. The root changelog and intentional same-path range updates remain replacements.
This is not a multi-file transaction. A later failure can leave earlier published parts; the error names the attempted output and acknowledged earlier outputs or part links before root rewriting. Temporary-cleanup and directory-sync errors say when publication has already happened, not that it was rolled back. A failed filesystem operation can also have an unconfirmed outcome; do not infer absence from its exit alone. A failed exclusive temporary open never authorizes cleanup of a pre-existing file at that temporary path.
Release-part numbering is checked while planning; an unrepresentable offset plus the starting part number is refused before any release part is published.
Refusals
The command names the actual problem:
- a missing
--line-limit, none of--spill,--versionand--entry, or--entrywithout--sectionand--file; - a limit that leaves no line after a part's own heading and preamble;
- an entry longer than the available room;
- a spilled-part link (
part-<N>.mdin a numbered folder underunreleased, or the olderunreleased/part-<N>.mdandunreleased-part-<N>.md) whose number does not read; stado.product.changelog.spill.part-unfiled: a linked part could not be moved into its numbered folder, naming both paths and what the system said;- a linked part that is missing or holds no
## Unreleasedsection; unreleased part number <NUMBER> is linked more than once: <FIRST-PATH> and <SECOND-PATH>;- a planned new output path that already exists, or cannot be inspected;
cannot spill after changelog part <LAST>: increment by <INCREMENT> exceeds the supported part-number range;cannot number spilled changelog part: first=<FIRST>, offset=<OFFSET>, sum exceeds the supported part-number range.
The two numbering refusals occur before the spill attempts to create a part or rewrite the root changelog. They introduce no new configurable limit.
Both modes also refuse repeated part numbers before writing changelog output. This includes two links to the same part and conflicting old/new path spellings that claim the same number; unique parts in either supported layout remain readable.
stado build submit refuses a revision whose Unreleased section still holds
entries or spilled-part links and names the --version command to run.
Stado Desktop
Use Products → Move CHANGELOG.md's Unreleased entries into changelog/. The repository checkout and line limit are required. Set either a release version or Spill. The form describes spilling without copying the backend's part path; the CLI owns that layout. The answer and every refusal, including numbering refusals, are shown as the command prints them.
Products → Write one change's own changelog entry file runs --entry: the
checkout, the line limit, the change's name, its kind (Fixed unless changed) and
the entry file on the selected host are required, and the answer or the declared
refusal is shown as the command prints it.
Source: this website