name: update-changelog description: Rules and workflows for updating docs/CHANGELOG.md, including version grouping, aggressive consolidation, and GitHub tag-linked release headings.
Automation Script (Recommended)
Use the helper script to generate a draft section from commits since the last tag, grouped into changelog categories, with similar commits consolidated by default (see notes below). The default output is intentionally compact: version headings are linked to the GitHub tag URL only when the tag exists, and bullet entries do not include commit hashes. Treat the script output as a draft: before applying or releasing, perform a human-quality grouping pass so repeated commits become concise user-facing bullets instead of a commit log dump.
Script path:
.ai/skills/update-changelog/scripts/update-changelog.ps1
Preview only (prints generated markdown):
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1
Generate draft from an explicit tag/version and save to a file:
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1 -FromTag v0.18.9 -Version 0.19.0 -OutputPath build/changelog-draft.md
Apply directly to docs/CHANGELOG.md:
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1 -FromTag v0.18.9 -Version 0.19.0 -Apply
Per-commit lines only (disables automatic similarity merge):
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1 -FromTag v0.18.9 -Version 0.19.0 -NoConsolidation
Include commit hashes only when explicitly requested for audit/debug work:
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1 -FromTag v0.18.9 -Version 0.19.0 -IncludeHashes
Notes:
-Versiondefaults to rootDirectory.Build.props.-FromTagdefaults togit describe --tags --abbrev=0.- The script upserts the version heading for the target version (replaces existing linked or unlinked section for that version or inserts after
## Unreleased). - Link version headings only when the corresponding Git tag exists locally or on
origin. Existing tag example:## [v0.22.236](https://github.com/ShareX/XerahS/releases/tag/v0.22.236). Unreleased/no-tag example:## v0.22.237. - Commit hashes are omitted by default to keep the changelog readable. Use
-IncludeHashesonly for temporary audit/debug drafts, not normal release notes. - Default consolidation:
Get-ConsolidationBucketinscripts/update-changelog.ps1merges commits that match the same similarity bucket (for example: ShareX.ImageEditor in the subject, 2026-... blog draft series, XIP/IEIP docs, Linux install/capture documentation, IEIP/XIP proposal.mdcreate/update under Changed, multipart / S3 multipart). Extend that function when new repetitive patterns appear. - Mandatory final compression pass: even when the script consolidates automatically, scan each category for adjacent or near-duplicate entries with the same component, feature area, document series, platform, dependency, or bug theme. Merge those into one readable bullet unless doing so would hide contributor attribution or combine unrelated behavior.
- Always manually review for wording, missed merges, and contributor attribution (
#PR,@user) before publishing.
Version Grouping Strategy
Current Unreleased Work
- Use the latest released tag as the default lower bound.
- Consolidate all commits after that tag into one heading for the target version, normally the root
Directory.Build.propsversion. - Do not create multiple patch or prerelease headings for the same unreleased range unless the user explicitly requests a historical reconstruction.
Historical Stable Release Reconstruction
- When rebuilding old changelog history across multiple stable releases, group entries by stable minor release boundaries.
- Use git tags and
Directory.Build.propshistory to identify those boundaries. - Fold patch and prerelease entries into the next stable minor heading unless a patch release was intentionally standalone.
- Retain original context if useful, for example:
Feature: ... (originally v0.8.1).
Consolidation Rules
- Combine related commits that affect the same component and purpose.
- Prefer semantic groups over literal commit prefixes: normalize minor wording differences like
add,update,finish,polish,refactor,wire,document, andfixwhen they describe the same user-facing workstream. - Use one bullet per component + intent cluster, not one bullet per commit. Good clusters include UI polish, installer/docs updates, proposal draft series, editor cleanup, upload provider work, dependency/build maintenance, and repeated bug fixes for the same behavior.
- Do not include raw commit hashes such as
65bccfb9in normal changelog entries. The linked version heading is the traceability anchor. - Keep different components separate unless they are part of one coherent user-facing change.
- Keep commits with external contributor attribution separate when merging would obscure credit.
- If a category still reads like a raw commit log after consolidation, it is not done; merge further or rewrite the bullets into concise release-note language.
Commit Entry Handling
Specific Commit Assignment
- Respect specific user requests to assign certain commits to specific versions.
- Example: "List commit
298457aunder v0.11.0." - Always verify the commit hash and subject before assignment, but do not print the hash in the final changelog unless the user explicitly asks for audit-style output.
Attribution
- External Contributors: Attribute Pull Requests from external contributors by including the PR number and their username.
- Format:
(#PR_NUMBER, @username) - Example:
(#77, @Hexeption)
- Format:
- Maintainer Merges: Exclude merge commits from the main maintainer (e.g.,
McoreD) from having explicit attribution unless they contain significant unique work not covered by other commits. The focus is on crediting other users.
Categorization
Group changes within each version using standard categories:
- Features: New functionality.
- Fixes: Bug fixes.
- Refactor: Code improvements without external behavior change.
- Build: Build system, dependencies, and packaging.
- Documentation: User, developer, proposal, and release documentation.
- Testing: Test coverage and test infrastructure.
- Performance: Performance improvements.
- Changed: Fallback for changes that do not map cleanly to the categories above.
The helper script maps infrastructure and chore-style commit types into Build unless the commit subject carries a clearer component/category signal.
Entry Consolidation to Reduce Line Count
CRITICAL: Consolidate related commits into single entries to keep the changelog concise and readable.
The automation script does this by default; agents should still edit the draft for narrative quality and any merges the heuristics miss.
Guidelines:
- Group by Component and Purpose: Combine multiple commits that affect the same component and serve the same purpose.
- Remove Commit Hashes: Normal changelog entries must not include commit hashes. Link the version heading to the GitHub tag only after the tag exists, for example
## [v0.22.236](https://github.com/ShareX/XerahS/releases/tag/v0.22.236). If the tag does not exist yet, use plain## v0.22.237. - Target Reduction: Aim for 50-80% line reduction by consolidating related work and removing hash lists.
Examples:
Before (verbose):
- **Media Explorer**: Add `IUploaderExplorer` interface
- **Media Explorer**: Implement S3 file browser
- **Media Explorer**: Implement Imgur album browser
- **Media Explorer**: Add navigation, breadcrumbs, search, filter
- **Media Explorer**: Add bandwidth savings banner
After (consolidated):
- **Media Explorer**: Implement provider file browsing with S3 and Imgur support, including navigation, search, filtering, and CDN thumbnail optimization
Before (mobile features):
- **Mobile**: Add adaptive mobile theming infrastructure
- **Mobile**: Refactor mobile views for adaptive native styling
- **Mobile**: Align mobile heads with native theming defaults
- **Mobile**: Complete sprint 5 mobile theming polish and docs
- **Mobile**: Add mobile upload queue and picker
- **Mobile**: Add mobile upload history screens
After (consolidated):
- **Mobile**: Add adaptive theming infrastructure with native styling polish
- **Mobile**: Add upload queue, picker, and history screens
Before (fixes):
- **Scrolling Capture**: Always auto-scroll to top
- **Scrolling Capture**: Apply workflow settings and refresh hotkeys
- **Scrolling Capture**: Use current scroll position for detection
After (consolidated):
- **Scrolling Capture**: Improve auto-scroll behavior and workflow settings integration
When NOT to Consolidate:
- Commits from different components (e.g., don't merge "Mobile" with "Linux Capture")
- Commits with external contributor attribution (keep separate for visibility)
- Significant standalone features that deserve their own entry
Format
Follow the Keep a Changelog format with Semantic Versioning.
## vX.Y.Z
Use `## [vX.Y.Z](https://github.com/ShareX/XerahS/releases/tag/vX.Y.Z)` only after `vX.Y.Z` exists as a local or remote Git tag.
### Features
- **Component**: Description
### Fixes
- Description
Workflow
Step-by-Step Process
Choose the Changelog Mode
- Current unreleased work: use the latest released tag as the lower bound and the root
Directory.Build.propsversion as the target heading. - Historical stable release reconstruction: use two stable release tags as the range and group output under the newer stable release heading.
- Current unreleased work: use the latest released tag as the lower bound and the root
Identify the Range
- Default helper-script behavior: omit
-FromTagto usegit describe --tags --abbrev=0. - Explicit current-range example:
-FromTag v0.21.2 -Version 0.22.0. - Historical stable reconstruction example: compare
v0.PREV_STABLE..v0.LATEST_STABLE.
Fallback tag listing:
git tag -l --sort=-version:refname | Select-Object -First 10- Default helper-script behavior: omit
Check Target Version Read the root
Directory.Build.props<Version>property unless the user explicitly provided a historical target version.Consolidate Version Headings
- Current unreleased work: create or update one
## vX.Y.Zheading for the target version when the tag does not exist yet. - Released/tagged work: use
## [vX.Y.Z](https://github.com/ShareX/XerahS/releases/tag/vX.Y.Z)only when the tag exists locally or onorigin. - Historical reconstruction: preserve stable release boundaries, but fold patch/prerelease fragments into the stable heading unless a patch release was intentionally standalone.
- Current unreleased work: create or update one
Categorize Commits
- Group commits into: Features, Fixes, Refactor, Build, Documentation, Testing, Performance, Changed
- Within each category, group by component (e.g., Mobile, Linux Capture, Editor)
Consolidate Related Entries
- Identify commits affecting the same component with similar purpose
- Merge them into single, comprehensive entries
- Remove raw commit hashes from final bullets
- Link the version heading to the GitHub tag URL only when the tag exists
- Aim for 50-80% reduction in line count
Format and Verify
- Ensure proper markdown formatting
- Verify linked version headings point to existing GitHub tags; unlink headings for tags that do not exist
- Verify normal bullets do not contain raw short hashes
- Check that external contributor attributions are preserved
- Confirm adherence to Keep a Changelog format
Fix Double-Encoding Mojibake After any write to
docs/CHANGELOG.md, scan for corrupted UTF-8 round-trip artifacts and replace them with their correct Unicode equivalents. This occurs when UTF-8 bytes are decoded and re-encoded by the wrong tool chain. Apply this as a final step every time:$c = [System.IO.File]::ReadAllText('docs/CHANGELOG.md', [System.Text.Encoding]::UTF8) # Fix mojibake involving the section-sign byte pair and normalize the output. $c = $c -replace [char]0x00C2 + [char]0x00A7, [char]0x2014 # Normalize any remaining section-sign corruption. $c = $c -replace [char]0x00C2 + [char]0x00A7, [char]0x00A7 # Collapse 3+ blank lines $c = $c -replace "\r?\n", "`n" $c = $c -replace "`n{3,}", "`n`n" $c = $c -replace "`n", "`r`n" [System.IO.File]::WriteAllText('docs/CHANGELOG.md', $c, [System.Text.Encoding]::UTF8)Common victims to watch for:
- em dash (
—, U+2014) - section sign (
§, U+00A7) - accented letters such as
ó(U+00F3)
- em dash (
Example Command Sequence
# Check latest tags with version-aware sorting.
$tags = git tag -l --sort=-version:refname | Select-Object -First 10
# Check current version.
$version = Select-String -Path "Directory.Build.props" -Pattern '<Version>(.*)</Version>' | ForEach-Object { $_.Matches.Groups[1].Value }
# Preview default current-unreleased changelog section.
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1
# Apply an explicit current-unreleased range.
powershell -NoProfile -ExecutionPolicy Bypass -File .ai/skills/update-changelog/scripts/update-changelog.ps1 -FromTag v0.21.2 -Version $version -Apply
Encoding-Safe Multi-Line Block Replacement
Why: exact-match replacement tools can fail when CHANGELOG.md contains multi-byte UTF-8 sequences that were double-encoded during a tool round trip. Use PowerShell [System.IO.File] plus Regex instead; it reads raw bytes and avoids exact-literal matching against corrupted text.
Pattern (replace all prerelease sections between two stable headings with new consolidated content):
$cl = 'docs/CHANGELOG.md'
$c = [System.IO.File]::ReadAllText($cl, [System.Text.Encoding]::UTF8)
$newSection = @'
## v0.X.Y
### Features
- ...
### Fixes
- ...
'@
# Use a linked heading instead only when v0.X.Y exists as a local or remote tag.
# (?s) = dotall (. matches newlines); match from first prerelease heading up to (but not including) the previous stable heading
$c = [System.Text.RegularExpressions.Regex]::Replace(
$c,
'(?s)## (?:v0\.FIRST_PRERELEASE|\[v0\.FIRST_PRERELEASE\]\([^)]+\)).*?(?=## (?:v0\.PREV_STABLE|\[v0\.PREV_STABLE\]\([^)]+\)))',
$newSection
)
# Normalize the double-encoded section-sign artifact (C2 A7 → A7).
$c = $c -replace [char]0x00C2 + [char]0x00A7, [char]0x00A7
# Collapse 3+ consecutive blank lines down to 2
$c = $c -replace "\r\n", "`n"
$c = $c -replace "`n{3,}", "`n`n"
$c = $c -replace "`n", "`r`n" # restore CRLF if the repo uses it
[System.IO.File]::WriteAllText($cl, $c, [System.Text.Encoding]::UTF8)
Key points:
(?s)makes.match newlines so the pattern spans the whole block.- The lookahead stops the match at the previous stable heading, linked or unlinked; it is not consumed.
- The mojibake normalization pass (
[char]0x00C2 + [char]0x00A7to[char]0x00A7) should always run after a regex write to guard against double-encoding. - Blank-line normalization (
\n{3,}to\n\n) prevents the file from accumulating excess whitespace after sections are removed.