update-changelog

star 273

Rules and workflows for updating docs/CHANGELOG.md, including version grouping, aggressive consolidation, and GitHub tag-linked release headings.

ShareX By ShareX schedule Updated 5/9/2026

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:

  • -Version defaults to root Directory.Build.props.
  • -FromTag defaults to git 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 -IncludeHashes only for temporary audit/debug drafts, not normal release notes.
  • Default consolidation: Get-ConsolidationBucket in scripts/update-changelog.ps1 merges 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 .md create/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.props version.
  • 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.props history 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, and fix when 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 65bccfb9 in 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 298457a under 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)
  • 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

  1. Choose the Changelog Mode

    • Current unreleased work: use the latest released tag as the lower bound and the root Directory.Build.props version 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.
  2. Identify the Range

    • Default helper-script behavior: omit -FromTag to use git 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
    
  3. Check Target Version Read the root Directory.Build.props <Version> property unless the user explicitly provided a historical target version.

  4. Consolidate Version Headings

    • Current unreleased work: create or update one ## vX.Y.Z heading 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 on origin.
    • Historical reconstruction: preserve stable release boundaries, but fold patch/prerelease fragments into the stable heading unless a patch release was intentionally standalone.
  5. 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)
  6. 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
  7. 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
  8. 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)

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]0x00A7 to [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.
Install via CLI
npx skills add https://github.com/ShareX/XerahS --skill update-changelog
Repository Details
star Stars 273
call_split Forks 14
navigation Branch main
article Path SKILL.md
More from Creator