Skip to content

Parser

internal/changes turns a generate-notes body into items. It is pure: it never imports internal/github, takes the body as a string, and returns plain structs, so every rule below is covered by table tests and golden files without HTTP.

FileHolds
item.goItem, Release (the items JSON), Group, Section
parse.goParse(body) []Item: line format, title format, classification
sort.goSort(items), Sections(items) and Section.Issues(): display order, issue deduplication
link.goLinkClosing(items, closing): the issues the pull requests close, for --link closing
notes.goNoteRefs(text, relativeTo) and CheckNotes(release, issueRepo, refs): what check-notes compares

Lines

Only * <title> by @<author> in <pr-url> lines become items. Everything else is ignored:

  • headings, including the ### categories a .github/release.yml adds — the result is the same with or without the file;
  • the ## New Contributors section, whose lines look like list items;
  • the **Full Changelog** line and the generated-by comment.

The title is matched greedily, so a title that contains by @ itself still splits at the last one. The PR URL may be on any host (GHES); the PR number comes from the URL. Lines are trimmed, so CRLF bodies parse too. Authors include bots: @dependabot[bot] gives author dependabot[bot] and bot: true — any [bot] suffix sets bot.

Titles

Strict, matching the amannn/action-semantic-pull-request check the target repositories enforce:

  • Format: <type>[(<scopes>)][!]: <subject>, with a lower-case type, a space after the colon, and a non-empty subject. ! sets breaking. More spaces before the subject are allowed, as the check allows them: fix(12): subject is a fix for #12, and its title is kept as written.
  • Types: feat fix docs style refactor perf test build ci chore revert.
  • Scopes are comma-separated and trimmed. Issue numbers are the digit scopes (\d+), in title order and deduplicated; e2e and every other scope are ignored. There are no +/&/and separators and no keyword fallback.
  • A title outside the format — GitHub’s Revert "…", a free-form title, an unknown or capitalised type — is Unclassified, and nothing is taken from it: empty type, scopes and issues, breaking: false.

Closing references

With --link closing, LinkClosing(items, closing) runs after parsing. closing is a plain map[int][]int — pull request to the issues it closes — that the command fetched through internal/github; this package never imports it.

  • The closing issues are added to issues after the title’s, ascending, without repeating one the title already has. The title’s order is kept, so an item still sorts by its first digit scope.
  • Every item gets issue_sources: title (the digit scopes, in title order) and closing (ascending), both never null. Without --link the field stays nil and is left out of the JSON.
  • The group never changes. An unclassified item can get issues this way: nothing is taken from its title, but a closing reference is not the title.
  • The items are sorted again, since an item without an issue may now have one.

Groups

GroupFrom
featuresfeat
fixesfix
otherdocs test ci refactor perf style build revert
maintenancechore
dependenciesa deps or deps-dev scope, or the author dependabot[bot]
unclassifiedanything outside the title format

Dependencies takes precedence over the type: fix(deps): … and chore(deps): … are dependencies, not bug fixes or maintenance. A dependabot pull request is a dependency even when its title is outside the format (Bump lodash from … to …); its type stays empty.

Order

Parse returns the items sorted, and the order is total, so the same body always gives the same bytes:

  1. By group, in the order of the table above.
  2. Within a group, by first issue number, then PR number; items without an issue go to the bottom, by PR number.
  3. Dependencies by PR number alone.

Sections returns the non-empty groups in that order, each with its items, for renderers that print a heading per group. Section.Issues collects the issue numbers of a group’s items, ascending and deduplicated, for the issue list.

Fixtures

Real generate-notes bodies under internal/changes/testdata/*.txt, each with a .golden items JSON beside it. They are anonymised: repositories are example-org/…, people are pseudonyms, and titles that would identify the source are reworded. Line structure, PR numbers, bot authors, package names and line endings are kept as captured, so the bodies still exercise the parser the way real ones do. Rewrite the goldens after an intended change with go test ./internal/changes -update (inside task dc:shell).

FixtureKind of releaseCovers
finalfinaldigit scopes, unclassified titles, New Contributors, dependabot
rcrelease candidatebuild(deps) from dependabot, free-form titles
patchpatchdeps and deps-dev scopes, a fix with a named scope
dependabotdependency updates onlydependabot deps-dev updates
release-ymlgenerated with .github/release.yml### category headings, CRLF line endings

A new fixture is anonymised the same way before it is committed.