diff --git a/BUILD.adoc b/BUILD.adoc index d4c9ffefe..b71f8aeee 100644 --- a/BUILD.adoc +++ b/BUILD.adoc @@ -47,6 +47,19 @@ the following command to find the next unused rule identifier. make next-rule-id ---- +== Generate Changelog + +To automatically generate changelog entries from git history using AI analysis, +use the following command. The script analyzes commits since the latest changelog +entry and generates properly formatted AsciiDoc entries for rule changes. + +[source,bash] +---- +make changelog +---- + +**Prerequisites:** The `zllm` command must be installed for this target to work. + == Generate Custom CSS In order to generate custom CSS we have to use diff --git a/Makefile b/Makefile index 7292e5369..8c54f4546 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DIRWORK := $(shell pwd -P) .PHONY: all clean install lint format pull assets rules html pdf epub force .PHONY: check check-rules check-rules-duplicates check-rules-incorrects -.PHONY: next-rule-id watch +.PHONY: next-rule-id watch changelog all: clean html rules clean: @@ -51,6 +51,9 @@ next-rule-id: "RULE_IDS=($$(grep -rh "^.*\[#[0-9]\{1,5\}.*$$" $(DIRCONTENTS) | sort -r))"; \ echo $$(($$(echo $${RULE_IDS[0]} | tr -d '\[' | tr -d '\]' | tr -d '#') + 1)); +changelog: + $(DIRSCRIPTS)/changelog.sh --update + assets: mkdir -p $(DIRBUILDS); cp -r assets $(DIRBUILDS)/; diff --git a/chapters/changelog.adoc b/chapters/changelog.adoc index 6024b6591..8fd6c91e9 100644 --- a/chapters/changelog.adoc +++ b/chapters/changelog.adoc @@ -71,7 +71,7 @@ to see a list of all changes. * `2020-06-30`: add details to <<114>> * `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>. * `2020-02-06`: new rule <<241>> -* `2020-02-05`: add Sunset header, clarify deprecation producedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>) +* `2020-02-05`: add Sunset header, clarify deprecation procedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>) * `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD) * `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>. * `2019-10-10`: remove never-implemented rule "{MUST} Permissions on events must correspond to API permissions" diff --git a/scripts/changelog.prompt b/scripts/changelog.prompt new file mode 100644 index 000000000..61761c4cb --- /dev/null +++ b/scripts/changelog.prompt @@ -0,0 +1,42 @@ +Write a changelog highlighting ONLY the rule changes based on the git commit messages and diffs provided. +Formatting, infrastructure, or similar non-rule changes should be omitted. + +The rule numbers should always be referenced using angle brackets. For example, rule 211 should be written as <<211>>. + +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]^ + +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/[SHORT_HASH]^ + +Example changelog: + +* `2021-12-09`: event id must not change in retry situations when producers <<211>>. +* `2021-11-24`: restructuring of the document and some rules. +* `2021-10-18`: new rule <<244>>. +* `2021-10-12`: improve clarity on {PATCH} usage in rule <<148>>. +* `2021-08-24`: improve clarity on {PUT} usage in rule <<148>>. +* `2021-08-24`: only use codes registered via IANA in rule <<243>>. +* `2021-08-17`: update formats per OpenAPI 3.1 in <<238>>. +* `2021-06-22`: <<238>> changed from {SHOULD} to {MUST}; consistency for rules around standards for data. +* `2021-06-03`: <<104>> with clear distinction of OpenAPI security schemes, favoring `bearer` to `oauth2`. +* `2021-06-01`: resolve uncertainties around 'occurred_at' semantics of <>. +* `2021-05-25`: <<172>> with <<114, API endpoint versioning>> as only custom media type usage exception. +* `2021-05-05`: define usage on resource-ids in {PUT} and {POST} in <<148>>. +* `2021-04-29`: improve clarity of <<133>>. +* `2021-03-19`: clarity on <<167>>. +* `2021-03-15`: <<242>> changed from {SHOULD} to {MUST}; improve clarity around <<203, event ordering>>. +* `2021-03-19`: best practice section <> +* `2021-02-16`: define how to reference models outside the api in <<234>>. +* `2021-02-15`: improve guideline <<176>> -- clients must be prepared to not receive problem return objects. +* `2021-01-19`: more details for {GET-with-Body} and {DELETE-with-Body} (<<148>>). +* `2020-09-29`: include models for headers to be included by reference in API definitions (<<183>>) +* `2020-09-08`: add exception for legacy host names to <<224>> +* `2020-08-25`: change <<240>> from {MUST} to {SHOULD}, explain exceptions +* `2020-08-25`: add exception for `self` to <<143>> and <<134>>. +* `2020-08-24`: change "{MUST} avoid trailing slashes" to <<136>>. +* `2020-08-20`: change <<183>> from {MUST} to {SHOULD}, mention gateway-specific headers (which are not part of the public API). +* `2020-06-30`: add details to <<114>> +* `2020-05-19`: new sections about DELETE with query parameters and {DELETE-with-Body} in <<148>>. +* `2020-02-06`: new rule <<241>> +* `2020-02-05`: add Sunset header, clarify deprecation procedure (<<185>>, <<186>>, <<187>>, <<188>>, <<189>>, <<190>>, <<191>>) +* `2020-01-21`: new rule <<240>> (as MUST, changed later to SHOULD) +* `2020-01-15`: change "Warning" to "Deprecation" header in <<189>>, <<190>>. diff --git a/scripts/changelog.sh b/scripts/changelog.sh new file mode 100755 index 000000000..0b6b045ae --- /dev/null +++ b/scripts/changelog.sh @@ -0,0 +1,181 @@ +#! /bin/bash + +# Generate changelog entries from git history using AI analysis +# Usage: ./changelog.sh [OPTIONS] [YEAR|DATE] +# +# By default (no arguments): +# - Detects latest changelog entry date from chapters/changelog.adoc +# - Generates entries for commits since that date +# - Outputs filtered changelog entries only +# +# Options: +# --raw Show full AI analysis output instead of filtered entries +# --update Write new entries to chapters/changelog.adoc (prepend to rule-changes section) +# --since DATE Use explicit date (format: YYYY-MM-DD) instead of auto-detecting +# --year YEAR Generate for a specific year (format: YYYY) +# +# Examples: +# ./changelog.sh # Auto-detect and generate since last entry +# ./changelog.sh --raw # Auto-detect with full output +# ./changelog.sh --update # Auto-detect and update the changelog file +# ./changelog.sh --since 2025-01-01 # Generate since specific date +# ./changelog.sh --year 2025 # Generate for entire year 2025 +# ./changelog.sh 2025 --raw # Legacy: year as positional argument with --raw + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +PROMPT_FILE="$SCRIPT_DIR/changelog.prompt" +CHANGELOG_FILE="$REPO_ROOT/chapters/changelog.adoc" + +# Parse arguments +MODE="auto" # auto, year, since +PARAM="" +RAW_OUTPUT=false +UPDATE_FILE=false + +# Handle legacy positional arguments first +if [[ ${#@} -gt 0 ]] && [[ "$1" =~ ^[0-9]{4}$ ]]; then + MODE="year" + PARAM="$1" + shift +fi + +# Parse remaining options +while [[ ${#@} -gt 0 ]]; do + case "$1" in + --raw) + RAW_OUTPUT=true + ;; + --update) + UPDATE_FILE=true + ;; + --year) + MODE="year" + PARAM="${2:-}" + if [[ -z "$PARAM" ]]; then + echo "Error: --year requires a year argument (YYYY)" >&2 + exit 1 + fi + shift + ;; + --since) + MODE="since" + PARAM="${2:-}" + if [[ -z "$PARAM" ]]; then + echo "Error: --since requires a date argument (YYYY-MM-DD)" >&2 + exit 1 + fi + shift + ;; + *) + echo "Error: Unknown option: $1" >&2 + exit 1 + ;; + esac + shift +done + +# Check dependencies +if ! command -v zllm &> /dev/null; then + echo "Error: 'zllm' command not found. Please install zllm to use this script." >&2 + exit 1 +fi + +if [[ ! -f "$PROMPT_FILE" ]]; then + echo "Error: Prompt file not found: $PROMPT_FILE" >&2 + exit 1 +fi + +# Determine the date range for git log +cd "$REPO_ROOT" + +case "$MODE" in + auto) + # Extract latest date from changelog.adoc + if [[ ! -f "$CHANGELOG_FILE" ]]; then + echo "Error: Changelog file not found: $CHANGELOG_FILE" >&2 + exit 1 + fi + + # Look for first date entry after "== Rule Changes" section + 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/') + + if [[ -z "$LATEST_DATE" ]]; then + echo "Error: Could not find any date entries in changelog" >&2 + exit 1 + fi + + echo "Latest changelog entry: $LATEST_DATE" >&2 + AFTER_DATE="$LATEST_DATE" + ;; + year) + if ! [[ "$PARAM" =~ ^[0-9]{4}$ ]]; then + echo "Error: Invalid year format. Expected YYYY, got: $PARAM" >&2 + exit 1 + fi + AFTER_DATE="$PARAM-01-01" + BEFORE_DATE="$PARAM-12-31" + ;; + since) + if ! [[ "$PARAM" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then + echo "Error: Invalid date format. Expected YYYY-MM-DD, got: $PARAM" >&2 + exit 1 + fi + AFTER_DATE="$PARAM" + ;; +esac + +# Get git log with diffs +if [[ -n "${BEFORE_DATE:-}" ]]; then + GIT_LOG=$(git log -p --after="$AFTER_DATE" --before="$BEFORE_DATE") +else + GIT_LOG=$(git log -p --after="$AFTER_DATE") +fi + +if [[ -z "$GIT_LOG" ]]; then + echo "No commits found since $AFTER_DATE" >&2 + exit 0 +fi + +# Pipe prompt and git log through zllm +OUTPUT=$(cat "$PROMPT_FILE" <(echo) <(echo "$GIT_LOG") | zllm) + +# Extract changelog entries (lines starting with '*') +ENTRIES=$(echo "$OUTPUT" | grep '^\*' || true) + +# Output or update +if [[ "$RAW_OUTPUT" == true ]]; then + echo "$OUTPUT" +elif [[ "$UPDATE_FILE" == true ]]; then + if [[ -z "$ENTRIES" ]]; then + echo "No changelog entries generated. Not updating file." >&2 + exit 0 + fi + + # Create temporary file with new entries inserted after "== Rule Changes" header + # Split file into: header + new entries + rest + HEADER_LINE=$(grep -n "^== Rule Changes$" "$CHANGELOG_FILE" | cut -d: -f1) + if [[ -z "$HEADER_LINE" ]]; then + echo "Error: '== Rule Changes' header not found in $CHANGELOG_FILE. Cannot update changelog." >&2 + exit 1 + fi + + { + # Read file up to (but not including) "== Rule Changes" + head -n "$((HEADER_LINE - 1))" "$CHANGELOG_FILE" + # Add the header and blank line + echo "== Rule Changes" + echo + # Add new entries + echo "$ENTRIES" + # Add existing entries (skip "== Rule Changes" line and the blank line after it) + tail -n +"$((HEADER_LINE + 2))" "$CHANGELOG_FILE" + } > "$CHANGELOG_FILE.tmp" + + mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE" + echo "Updated $CHANGELOG_FILE with new entries" >&2 +else + echo "$ENTRIES" +fi