Conventional-Commit-driven version bumps (scripts/bump.py), a commit-msg validation hook, and a /release-notes workflow that produces a human-reviewed CHANGELOG.md. Adds an in-app version badge + 'What's new' dialog on the launcher. The version is single-sourced from pyproject.toml (cim_suite.__version__), and the deterministic bump backbone lives in scripts/release/ with tests in tests/release/. Marks the 1.0.0 baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
#!/usr/bin/env python
|
|
"""Collect commits since the last release and emit a digest for changelog drafting.
|
|
|
|
This produces the STRUCTURED INPUT for the human + Claude changelog step. It never
|
|
writes CHANGELOG.md and never invents prose — it just buckets commits into
|
|
user-visible features/fixes/breaking changes vs. collapsible internal work, using the
|
|
same range logic as the bump script. The plain-language summary is drafted by Claude
|
|
from this digest and reviewed before it lands (see ``.claude/commands/release-notes.md``).
|
|
|
|
python scripts/release_notes.py # human-readable digest
|
|
python scripts/release_notes.py --json # machine-readable digest (for Claude)
|
|
python scripts/release_notes.py --baseline <rev>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
|
from release import repo # noqa: E402
|
|
from release.commits import ParsedCommit, build_digest # noqa: E402
|
|
|
|
|
|
def _commit_dict(c: ParsedCommit) -> dict[str, object]:
|
|
return {
|
|
"sha": c.sha[:8],
|
|
"type": c.type,
|
|
"scope": c.scope,
|
|
"breaking": c.breaking,
|
|
"description": c.description,
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--json", action="store_true", help="emit JSON for tooling/Claude")
|
|
parser.add_argument("--baseline", help="range start commit/tag when no version tag exists")
|
|
args = parser.parse_args(argv)
|
|
|
|
plan = repo.gather(baseline=args.baseline)
|
|
digest = build_digest(plan.commits)
|
|
bump = plan.detected_bump or "patch"
|
|
suggested = plan.current.bump(bump)
|
|
|
|
if args.json:
|
|
payload = {
|
|
"current_version": str(plan.current),
|
|
"suggested_version": str(suggested),
|
|
"suggested_bump": bump,
|
|
"detected_bump": plan.detected_bump,
|
|
"range": plan.range_spec,
|
|
"features": [_commit_dict(c) for c in digest.features],
|
|
"fixes": [_commit_dict(c) for c in digest.fixes],
|
|
"breaking": [_commit_dict(c) for c in digest.breaking],
|
|
"internal_count": len(digest.internal),
|
|
"internal_types": sorted({c.type or "other" for c in digest.internal}),
|
|
}
|
|
print(json.dumps(payload, indent=2))
|
|
return 0
|
|
|
|
rng = plan.range_spec or "(all commits)"
|
|
print(f"# Release digest ({plan.current} -> {suggested}, {bump})")
|
|
print(f"Range: {rng} Commits: {len(plan.commits)}\n")
|
|
for title, items in (
|
|
("Breaking changes", digest.breaking),
|
|
("Features (feat)", digest.features),
|
|
("Fixes (fix)", digest.fixes),
|
|
):
|
|
print(f"## {title} ({len(items)})")
|
|
for c in items:
|
|
scope = f"({c.scope})" if c.scope else ""
|
|
print(f" - {c.sha[:8]} {c.type}{scope}: {c.description}")
|
|
print()
|
|
types = sorted({c.type or "other" for c in digest.internal})
|
|
print(f"## Internal (collapse to one line): {len(digest.internal)} commit(s) {types}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|