Rework the changelog and release flow to remove the aspirational
top-level version that made it unclear whether new entries were
landing in an already-released section.
Schema change: `changelog.json` now has an explicit `unreleased`
object instead of hoisting the upcoming version to the top level. The
old `{version, sections, history}` layout becomes
`{unreleased, history}`, with the previously-released version moved
into `history[0]`.
New entries always go into `unreleased` via `changelog.py`. Releasing
is a single atomic operation (`release.py X.Y.Z`) that promotes
`unreleased` into `history[0]` with the target version number,
propagates the version to every source file, and regenerates the
markdown artifacts.
Script renames:
- `_changelog.py` → `changelog_lib.py` (no more underscore-prefixed
module that's imported by two siblings)
- `changelog-add.py` → `changelog.py`
- `update-version.py` → `release.py` (does more than just version
propagation — the name now reflects the full release action)
CHANGELOG.md rendering follows Keep a Changelog: a `## [Unreleased]`
block appears on top only when there are unreleased entries; the
update-json/changelog.md shown in Magisk/KSU popups still skips
Unreleased (only released versions make sense there).
Docs (docs/changelog.md, docs/releasing.md, CONTRIBUTING.md, CLAUDE.md)
updated with the new commands and the clarified model.
CLAUDE.md additionally gains a "read these before doing any work"
section that lists the contributor docs — so future sessions load the
workflow rules into context instead of skipping them as optional.
2 KiB
Changelog
Source of truth
lsposed/app/src/main/assets/changelog.json — bilingual (en/ru), full history.
Schema:
{
"unreleased": {
"sections": [
{ "type": "fixed", "items": [{"en": "...", "ru": "..."}] }
]
},
"history": [
{ "version": "0.6.1", "sections": [...] },
{ "version": "0.6.0", "sections": [...] }
]
}
unreleased collects entries during development. history[0] is the most recent released version. The top-level structure is flat — there is no aspirational "upcoming version" field; the name is fixed only when release.py is run.
Generated files (do NOT edit by hand)
Two markdown files are regenerated from the JSON by scripts/changelog_lib.py:
CHANGELOG.mdat repo root — full history, Keep a Changelog format. Renders## [Unreleased]on top when that section has entries, then each history entry as## vX.Y.Z. CI extracts a single## vX.Y.Zblock for the GitHub release body, so don't edit release notes by hand either.update-json/changelog.md— last 5 released versions only (no Unreleased block). Shown by Magisk/KSU in the update popup inside the manager app.
Adding an entry
From a PR branch:
./scripts/changelog.py <type> "<EN text>" "<RU text>"
Types: added, changed, fixed, removed, deprecated, security.
The entry lands in unreleased.sections. Both markdown files are regenerated automatically. Commit lsposed/app/src/main/assets/changelog.json, CHANGELOG.md, and update-json/changelog.md alongside your code change.
When to add an entry
Add one for user-visible changes:
- new features / behaviour changes
- bug fixes that affect released versions
- security fixes
- breaking changes (also bump major/minor as appropriate)
Skip for: internal refactors with no behaviour change, documentation-only changes, CI/build tweaks, test additions.
Cutting a release
See releasing.md. The release script promotes unreleased into history[0] atomically with the version bump — there is no separate "rotate" step.