text
| 1 | # Changelog Generator — Workflow |
| 2 | |
| 3 | Turns a git commit range into a grouped, human-readable `CHANGELOG.md` entry: features, |
| 4 | fixes, and breaking changes separated out, each linked to its issue/PR where possible. |
| 5 | Works best on a history following Conventional Commits (see `openagents/commit-conventions`, |
| 6 | listed as a soft dependency), but degrades gracefully on unstructured history too. |
| 7 | |
| 8 | Inputs: `from_ref` (required), `to_ref` (default `HEAD`), `version` (optional; section |
| 9 | is labeled "Unreleased" if omitted). |
| 10 | |
| 11 | ## Step 1 — Collect the commit range |
| 12 | |
| 13 | ```bash |
| 14 | git log <from_ref>..<to_ref> --pretty=format:'%H|%s|%b|%an' --no-merges |
| 15 | ``` |
| 16 | |
| 17 | - Exclude merge commits (`--no-merges`) — they're usually noise for a changelog; if the |
| 18 | project's workflow is merge-commit-based instead of squash/rebase, adjust and include |
| 19 | merge commit subjects instead, but say so. |
| 20 | - Also gather: `git diff <from_ref>..<to_ref> --stat` for a sanity check on scope, and |
| 21 | the repository's issue/PR URL pattern (from the remote `origin` URL) to build links. |
| 22 | |
| 23 | ## Step 2 — Parse and classify each commit |
| 24 | |
| 25 | For each commit subject, extract `type(scope): summary` if it matches Conventional |
| 26 | Commits. For commits that don't match the format: |
| 27 | - Infer a type from the diff/summary content as a best effort (e.g. a summary starting |
| 28 | with "add" → likely `feat`; "fix"/"resolve" → `fix`). |
| 29 | - Mark these as inferred (not explicit) — if the output format distinguishes, note it; |
| 30 | regardless, still classify rather than dropping them, since a changelog should |
| 31 | reflect all user-relevant history in the range, not just the well-formatted commits. |
| 32 | |
| 33 | Group by type: |
| 34 | - `feat` → **Added** / **Features** |
| 35 | - `fix` → **Fixed** |
| 36 | - `perf` → **Performance** |
| 37 | - `refactor`, `style`, `chore`, `build`, `ci`, `test` → generally omit from a |
| 38 | user-facing changelog (internal-only) *unless* the body indicates user-visible |
| 39 | impact — use judgment, don't include noise. |
| 40 | - `docs` → **Documentation** (only if the project's changelog convention includes |
| 41 | docs; otherwise omit) |
| 42 | - Anything with a `BREAKING CHANGE:` footer or `!` after the type/scope → **Breaking |
| 43 | Changes**, always included regardless of type, and always listed first within its |
| 44 | section. |
| 45 | |
| 46 | ## Step 3 — Deduplicate and merge |
| 47 | |
| 48 | - Multiple commits that are really one logical change (e.g. a feature commit plus a |
| 49 | follow-up fix commit for the same feature within the range) should be merged into |
| 50 | one changelog line where that reads more clearly — use judgment, don't force it. |
| 51 | - Revert commits that cancel out an earlier commit in the *same* range should cancel |
| 52 | each other out of the changelog entirely (net-zero user impact). |
| 53 | |
| 54 | ## Step 4 — Add links |
| 55 | |
| 56 | For each entry, if the commit footer references an issue/PR number (`Fixes #123`, |
| 57 | `Refs #456`) or the commit is reachable via a known PR (e.g. via `gh pr list --search |
| 58 | <sha>` if `gh` is available), append a link: `([#123](<repo-url>/issues/123))`. If no |
| 59 | reference is found, link the commit itself: `([abc1234](<repo-url>/commit/<sha>))`. |
| 60 | Don't fabricate a link if neither is determinable — omit it for that entry instead. |
| 61 | |
| 62 | ## Step 5 — Write the entry |
| 63 | |
| 64 | Follow [Keep a Changelog](https://keepachangelog.com/) structure: |
| 65 | |
| 66 | ```markdown |
| 67 | ## [<version or "Unreleased">] - <YYYY-MM-DD, omit date if Unreleased> |
| 68 | |
| 69 | ### Breaking Changes |
| 70 | - <description of the break and migration path, if given in the commit body> ([#123](...)) |
| 71 | |
| 72 | ### Added |
| 73 | - <summary, imperative→noun-phrase form, e.g. "add fuzzy search to package search"> ([#123](...)) |
| 74 | |
| 75 | ### Fixed |
| 76 | - <summary> ([#456](...)) |
| 77 | |
| 78 | ### Performance |
| 79 | - <summary> ([#789](...)) |
| 80 | ``` |
| 81 | |
| 82 | Omit any section with zero entries — don't print an empty `### Fixed` heading. |
| 83 | |
| 84 | Insert this new section at the top of `CHANGELOG.md`, directly under the file's H1 |
| 85 | title (create the file with a standard header if it doesn't exist yet). Do not |
| 86 | reformat or reflow existing changelog entries below the insertion point. |
| 87 | |
| 88 | ## Step 6 — Review pass |
| 89 | |
| 90 | Before finalizing, re-read the generated entries as a *user* of the project, not the |
| 91 | author: does each line make sense without repo-internal context (variable names, |
| 92 | internal file paths)? Rewrite any entry that only makes sense to someone who read the |
| 93 | diff. Flag (but don't silently drop) any commit whose purpose is genuinely unclear from |
| 94 | its message alone — list it under a `### Uncategorized` section at the bottom for a |
| 95 | human to triage, rather than guessing at user-facing wording. |
| 96 | |
| 97 | ## Stop conditions |
| 98 | |
| 99 | - `from_ref` doesn't exist or the range is empty → report this, don't generate an empty |
| 100 | section. |
| 101 | - Range contains more than ~150 commits → note that the changelog may be coarse and |
| 102 | offer to summarize by theme instead of enumerating every commit. |
| 103 |