Skip to content

Commit 075dbb1

Browse files
SmilyOrgclaude
andauthored
Add changelog generation script and tools (#856)
* Add changelog generation script and tools Introduces a production-ready changelog generation system based on PR #816 discussion. Features: - scripts/changelog.sh: Automated script to generate changelog entries from git history - scripts/changelog.prompt: AI prompt instructions for identifying rule changes - Makefile target: 'make changelog' auto-detects latest entry and updates in place The tool analyzes git commits for a specified time period, identifies rule-related changes (filtering out formatting, infrastructure, and editorial changes), and formats them as AsciiDoc changelog entries with proper cross-references and PR/commit links. Usage: Direct: ./scripts/changelog.sh [OPTIONS] Make: make changelog Options: --raw Show full AI analysis output --update Write entries to chapters/changelog.adoc (default for make) --year YYYY Generate for entire year --since YYYY-MM-DD Generate since specific date By default (no args), auto-detects latest changelog entry and generates for commits after that date. Requirements: - zllm command installed (Zalando internal tool) - Git repository Co-Authored-By: Claude Code <noreply@anthropic.com> * Address PR review comments on changelog generation tool - Fix spelling: "producedure" → "procedure" in both changelog.prompt and chapters/changelog.adoc - Fix shebang format to match project conventions (#! /bin/bash) - Replace non-portable grep -oP with grep -E + sed for macOS compatibility - Add error handling for missing "== Rule Changes" header before arithmetic operations Co-Authored-By: Claude Code <noreply@anthropic.com> * Document make changelog target in BUILD.adoc Add a new section describing the changelog generation tool, its prerequisites, and usage options to match existing documentation practices for other Makefile targets. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 6750588 commit 075dbb1

File tree

5 files changed

+241
-2
lines changed

5 files changed

+241
-2
lines changed

BUILD.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ the following command to find the next unused rule identifier.
4747
make next-rule-id
4848
----
4949

50+
== Generate Changelog
51+
52+
To automatically generate changelog entries from git history using AI analysis,
53+
use the following command. The script analyzes commits since the latest changelog
54+
entry and generates properly formatted AsciiDoc entries for rule changes.
55+
56+
[source,bash]
57+
----
58+
make changelog
59+
----
60+
61+
**Prerequisites:** The `zllm` command must be installed for this target to work.
62+
5063
== Generate Custom CSS
5164

5265
In order to generate custom CSS we have to use

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ DIRWORK := $(shell pwd -P)
1111

1212
.PHONY: all clean install lint format pull assets rules html pdf epub force
1313
.PHONY: check check-rules check-rules-duplicates check-rules-incorrects
14-
.PHONY: next-rule-id watch
14+
.PHONY: next-rule-id watch changelog
1515

1616
all: clean html rules
1717
clean:
@@ -51,6 +51,9 @@ next-rule-id:
5151
"RULE_IDS=($$(grep -rh "^.*\[#[0-9]\{1,5\}.*$$" $(DIRCONTENTS) | sort -r))"; \
5252
echo $$(($$(echo $${RULE_IDS[0]} | tr -d '\[' | tr -d '\]' | tr -d '#') + 1));
5353

54+
changelog:
55+
$(DIRSCRIPTS)/changelog.sh --update
56+
5457
assets:
5558
mkdir -p $(DIRBUILDS);
5659
cp -r assets $(DIRBUILDS)/;

chapters/changelog.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ to see a list of all changes.
7171
* `2020-06-30`: add details to <<114>>
7272
* `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>.
7373
* `2020-02-06`: new rule <<241>>
74-
* `2020-02-05`: add Sunset header, clarify deprecation producedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>)
74+
* `2020-02-05`: add Sunset header, clarify deprecation procedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>)
7575
* `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD)
7676
* `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>.
7777
* `2019-10-10`: remove never-implemented rule "{MUST} Permissions on events must correspond to API permissions"

scripts/changelog.prompt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Write a changelog highlighting ONLY the rule changes based on the git commit messages and diffs provided.
2+
Formatting, infrastructure, or similar non-rule changes should be omitted.
3+
4+
The rule numbers should always be referenced using angle brackets. For example, rule 211 should be written as <<211>>.
5+
6+
At the end of every changelog row, you should add a link to the related Pull Request in AsciiDoc format, like so: ^https://github.com/zalando/restful-api-guidelines/pull/<PR_NUMBER>[#PR_NUMBER]^
7+
8+
If there is no relevant Pull Request, you should link to a commit in asciidoc format instead, like so: ^https://github.com/zalando/restful-api-guidelines/commit/<COMMIT_HASH>[SHORT_HASH]^
9+
10+
Example changelog:
11+
12+
* `2021-12-09`: event id must not change in retry situations when producers <<211>>.
13+
* `2021-11-24`: restructuring of the document and some rules.
14+
* `2021-10-18`: new rule <<244>>.
15+
* `2021-10-12`: improve clarity on {PATCH} usage in rule <<148>>.
16+
* `2021-08-24`: improve clarity on {PUT} usage in rule <<148>>.
17+
* `2021-08-24`: only use codes registered via IANA in rule <<243>>.
18+
* `2021-08-17`: update formats per OpenAPI 3.1 in <<238>>.
19+
* `2021-06-22`: <<238>> changed from {SHOULD} to {MUST}; consistency for rules around standards for data.
20+
* `2021-06-03`: <<104>> with clear distinction of OpenAPI security schemes, favoring `bearer` to `oauth2`.
21+
* `2021-06-01`: resolve uncertainties around 'occurred_at' semantics of <<event-metadata, event metadata>>.
22+
* `2021-05-25`: <<172>> with <<114, API endpoint versioning>> as only custom media type usage exception.
23+
* `2021-05-05`: define usage on resource-ids in {PUT} and {POST} in <<148>>.
24+
* `2021-04-29`: improve clarity of <<133>>.
25+
* `2021-03-19`: clarity on <<167>>.
26+
* `2021-03-15`: <<242>> changed from {SHOULD} to {MUST}; improve clarity around <<203, event ordering>>.
27+
* `2021-03-19`: best practice section <<cursor-based-pagination>>
28+
* `2021-02-16`: define how to reference models outside the api in <<234>>.
29+
* `2021-02-15`: improve guideline <<176>> -- clients must be prepared to not receive problem return objects.
30+
* `2021-01-19`: more details for {GET-with-Body} and {DELETE-with-Body} (<<148>>).
31+
* `2020-09-29`: include models for headers to be included by reference in API definitions (<<183>>)
32+
* `2020-09-08`: add exception for legacy host names to <<224>>
33+
* `2020-08-25`: change <<240>> from {MUST} to {SHOULD}, explain exceptions
34+
* `2020-08-25`: add exception for `self` to <<143>> and <<134>>.
35+
* `2020-08-24`: change "{MUST} avoid trailing slashes" to <<136>>.
36+
* `2020-08-20`: change <<183>> from {MUST} to {SHOULD}, mention gateway-specific headers (which are not part of the public API).
37+
* `2020-06-30`: add details to <<114>>
38+
* `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>.
39+
* `2020-02-06`: new rule <<241>>
40+
* `2020-02-05`: add Sunset header, clarify deprecation procedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>)
41+
* `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD)
42+
* `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>.

scripts/changelog.sh

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#! /bin/bash
2+
3+
# Generate changelog entries from git history using AI analysis
4+
# Usage: ./changelog.sh [OPTIONS] [YEAR|DATE]
5+
#
6+
# By default (no arguments):
7+
# - Detects latest changelog entry date from chapters/changelog.adoc
8+
# - Generates entries for commits since that date
9+
# - Outputs filtered changelog entries only
10+
#
11+
# Options:
12+
# --raw Show full AI analysis output instead of filtered entries
13+
# --update Write new entries to chapters/changelog.adoc (prepend to rule-changes section)
14+
# --since DATE Use explicit date (format: YYYY-MM-DD) instead of auto-detecting
15+
# --year YEAR Generate for a specific year (format: YYYY)
16+
#
17+
# Examples:
18+
# ./changelog.sh # Auto-detect and generate since last entry
19+
# ./changelog.sh --raw # Auto-detect with full output
20+
# ./changelog.sh --update # Auto-detect and update the changelog file
21+
# ./changelog.sh --since 2025-01-01 # Generate since specific date
22+
# ./changelog.sh --year 2025 # Generate for entire year 2025
23+
# ./changelog.sh 2025 --raw # Legacy: year as positional argument with --raw
24+
25+
set -euo pipefail
26+
27+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
29+
PROMPT_FILE="$SCRIPT_DIR/changelog.prompt"
30+
CHANGELOG_FILE="$REPO_ROOT/chapters/changelog.adoc"
31+
32+
# Parse arguments
33+
MODE="auto" # auto, year, since
34+
PARAM=""
35+
RAW_OUTPUT=false
36+
UPDATE_FILE=false
37+
38+
# Handle legacy positional arguments first
39+
if [[ ${#@} -gt 0 ]] && [[ "$1" =~ ^[0-9]{4}$ ]]; then
40+
MODE="year"
41+
PARAM="$1"
42+
shift
43+
fi
44+
45+
# Parse remaining options
46+
while [[ ${#@} -gt 0 ]]; do
47+
case "$1" in
48+
--raw)
49+
RAW_OUTPUT=true
50+
;;
51+
--update)
52+
UPDATE_FILE=true
53+
;;
54+
--year)
55+
MODE="year"
56+
PARAM="${2:-}"
57+
if [[ -z "$PARAM" ]]; then
58+
echo "Error: --year requires a year argument (YYYY)" >&2
59+
exit 1
60+
fi
61+
shift
62+
;;
63+
--since)
64+
MODE="since"
65+
PARAM="${2:-}"
66+
if [[ -z "$PARAM" ]]; then
67+
echo "Error: --since requires a date argument (YYYY-MM-DD)" >&2
68+
exit 1
69+
fi
70+
shift
71+
;;
72+
*)
73+
echo "Error: Unknown option: $1" >&2
74+
exit 1
75+
;;
76+
esac
77+
shift
78+
done
79+
80+
# Check dependencies
81+
if ! command -v zllm &> /dev/null; then
82+
echo "Error: 'zllm' command not found. Please install zllm to use this script." >&2
83+
exit 1
84+
fi
85+
86+
if [[ ! -f "$PROMPT_FILE" ]]; then
87+
echo "Error: Prompt file not found: $PROMPT_FILE" >&2
88+
exit 1
89+
fi
90+
91+
# Determine the date range for git log
92+
cd "$REPO_ROOT"
93+
94+
case "$MODE" in
95+
auto)
96+
# Extract latest date from changelog.adoc
97+
if [[ ! -f "$CHANGELOG_FILE" ]]; then
98+
echo "Error: Changelog file not found: $CHANGELOG_FILE" >&2
99+
exit 1
100+
fi
101+
102+
# Look for first date entry after "== Rule Changes" section
103+
LATEST_DATE=$(grep -E '^\* `[0-9]{4}-[0-9]{2}-[0-9]{2}' "$CHANGELOG_FILE" | head -1 | sed -E 's/^\* `([0-9]{4}-[0-9]{2}-[0-9]{2}).*/\1/')
104+
105+
if [[ -z "$LATEST_DATE" ]]; then
106+
echo "Error: Could not find any date entries in changelog" >&2
107+
exit 1
108+
fi
109+
110+
echo "Latest changelog entry: $LATEST_DATE" >&2
111+
AFTER_DATE="$LATEST_DATE"
112+
;;
113+
year)
114+
if ! [[ "$PARAM" =~ ^[0-9]{4}$ ]]; then
115+
echo "Error: Invalid year format. Expected YYYY, got: $PARAM" >&2
116+
exit 1
117+
fi
118+
AFTER_DATE="$PARAM-01-01"
119+
BEFORE_DATE="$PARAM-12-31"
120+
;;
121+
since)
122+
if ! [[ "$PARAM" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
123+
echo "Error: Invalid date format. Expected YYYY-MM-DD, got: $PARAM" >&2
124+
exit 1
125+
fi
126+
AFTER_DATE="$PARAM"
127+
;;
128+
esac
129+
130+
# Get git log with diffs
131+
if [[ -n "${BEFORE_DATE:-}" ]]; then
132+
GIT_LOG=$(git log -p --after="$AFTER_DATE" --before="$BEFORE_DATE")
133+
else
134+
GIT_LOG=$(git log -p --after="$AFTER_DATE")
135+
fi
136+
137+
if [[ -z "$GIT_LOG" ]]; then
138+
echo "No commits found since $AFTER_DATE" >&2
139+
exit 0
140+
fi
141+
142+
# Pipe prompt and git log through zllm
143+
OUTPUT=$(cat "$PROMPT_FILE" <(echo) <(echo "$GIT_LOG") | zllm)
144+
145+
# Extract changelog entries (lines starting with '*')
146+
ENTRIES=$(echo "$OUTPUT" | grep '^\*' || true)
147+
148+
# Output or update
149+
if [[ "$RAW_OUTPUT" == true ]]; then
150+
echo "$OUTPUT"
151+
elif [[ "$UPDATE_FILE" == true ]]; then
152+
if [[ -z "$ENTRIES" ]]; then
153+
echo "No changelog entries generated. Not updating file." >&2
154+
exit 0
155+
fi
156+
157+
# Create temporary file with new entries inserted after "== Rule Changes" header
158+
# Split file into: header + new entries + rest
159+
HEADER_LINE=$(grep -n "^== Rule Changes$" "$CHANGELOG_FILE" | cut -d: -f1)
160+
if [[ -z "$HEADER_LINE" ]]; then
161+
echo "Error: '== Rule Changes' header not found in $CHANGELOG_FILE. Cannot update changelog." >&2
162+
exit 1
163+
fi
164+
165+
{
166+
# Read file up to (but not including) "== Rule Changes"
167+
head -n "$((HEADER_LINE - 1))" "$CHANGELOG_FILE"
168+
# Add the header and blank line
169+
echo "== Rule Changes"
170+
echo
171+
# Add new entries
172+
echo "$ENTRIES"
173+
# Add existing entries (skip "== Rule Changes" line and the blank line after it)
174+
tail -n +"$((HEADER_LINE + 2))" "$CHANGELOG_FILE"
175+
} > "$CHANGELOG_FILE.tmp"
176+
177+
mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE"
178+
echo "Updated $CHANGELOG_FILE with new entries" >&2
179+
else
180+
echo "$ENTRIES"
181+
fi

0 commit comments

Comments
 (0)