Skip to content

[feature] Read Conventional Commits by default - #153

Merged
namolnad merged 3 commits into
mainfrom
feature/conventional-commits
Aug 14, 2026
Merged

[feature] Read Conventional Commits by default#153
namolnad merged 3 commits into
mainfrom
feature/conventional-commits

Conversation

@namolnad

Copy link
Copy Markdown
Owner

Warning

BREAKING CHANGE — commit messages are read as Conventional Commits rather than Finch's [tag] description. Projects on the original convention keep it with one line:

format:
  commit_style: delimited

Addresses the complaint that Finch asked for a convention it had invented rather than the one everybody else uses.

How it reads

type(scope): description. The type and every scope become tags, so feat(app-store): … carries both feat and app-store — which is what --required-tags app-store matches on, unchanged. A ! before the colon, or a line opening BREAKING CHANGE:, adds a breaking tag.

breaking leads the tag list, because sections are matched in tag order and a breaking change belongs under that heading ahead of its own type. The default sections gain one for it, and fix joins Bug Fixes so the defaults suit either convention.

Multiple entries per commit (#131) composes with it — one commit, two sections:

feat: better support for AnyEncodables (#1022)
fix(codable): drop the legacy path

Where it lives

CommitParser now owns every read of a commit message — which tags it carries, what is left to print, whether a line opens an entry — where those three questions used to be three regular expressions consulted from two files.

Body lines are stricter than subjects, and here's why

A subject is known to be a commit message, so any lower-case type is read from it, custom types included. A body line is not. Running this against Finch's own history turned a wrapped paragraph into an entry:

…That was right for the build and wrong for the
tests: the file is regenerated from the build graph by anything which…

- The file is regenerated from the build graph by anything which knows nothing of FINCH_TESTS…

So body lines are held to the types the specification names. Prose which merely contains a colon — Note: …, Fixes: #123 — was never eligible, since a type must be lower case; that is also why BREAKING CHANGE: is matched separately rather than as a type.

I first tried allowing the project's own section tags as body types. That backfired on this very repo, which configures tests as a section tag — so the union re-admitted the exact line it was meant to exclude. Dropped it.

Two fixes carried along

  • Tags come from the message, not the whole log line. An author address of dependabot[bot]@… had been contributing a phantom bot tag to every dependabot commit. Visible whenever a format string prints tags.
  • [tag]: description no longer keeps its colon once the tag is removed. Dependabot writes exactly that shape, so it appeared in this repo's changelog as - : Bump github.com/mxcl/version….

This repo eats it

.finch/config.release.yml opts into delimited, because Finch's own history predates all of this. Without that line its release notes come out empty — I ran it to be sure. That is the migration in miniature, and worth having in the tree.

Test plan

  • 42 tests, 0 failures — up from 32. New CommitParserTests covers types, multi-scope, both breaking forms, untagged subjects, prose-with-colon rejection, and that the delimited style is unchanged.
  • The whole pre-existing suite still runs against commit_style: delimited, which is the compatibility proof — those fixtures and snapshots are untouched apart from the new config key.
  • New end-to-end snapshot over a conventional log, including a dependabot commit (no phantom tag) and a body containing prose and bullets (not entries).
  • Ran the binary over this repo's real 0.3.0..main history under both styles.
  • swiftformat --lint . clean.

🤖 Generated with Claude Code

BREAKING CHANGE: commit messages are now read as Conventional Commits,
`type(scope): description`, rather than Finch's `[tag] description`. Projects
using the original convention keep it with one line — `commit_style: delimited`
under `format:` — and nothing else about their configuration changes.

Finch asked for a convention it had invented, which is the complaint. The type
and every scope become tags, so `feat(app-store): …` carries both `feat` and
`app-store` and `--required-tags app-store` goes on matching. A `!` before the
colon, or a line opening `BREAKING CHANGE:`, adds a `breaking` tag, which leads
the tag list so that a breaking change is filed under that heading ahead of its
own type. The default sections gain one for it, and `fix` joins Bug Fixes so
the defaults suit either convention.

All of this lands in one place. `CommitParser` now owns every read of a commit
message — which tags it carries, what is left to print, and whether a line opens
an entry of its own — where those three questions used to be three regular
expressions consulted from two files.

Body lines are held to a stricter standard than subjects. A subject is known to
be a commit message, so any lower-case type is read from it, custom types
included. A body line is not: running this against Finch's own history turned
a wrapped paragraph — `…was right for the build and wrong for the` /
`tests: the file is regenerated…` — into a `tests` entry. Body lines are
therefore limited to the types the specification names. Prose which merely
contains a colon, `Note: …` or `Fixes: #123`, was never eligible: a type must be
lower case, which is also why `BREAKING CHANGE:` is matched separately.

Tags now come from the message rather than the whole log line, which is a fix in
its own right: an author address of `dependabot[bot]@…` had been contributing a
phantom `bot` tag to every dependabot commit. While there, a `[tag]: description`
subject — dependabot writes exactly that — no longer leaves its colon behind
once the tag is removed.

This project's own history predates all of this, so .finch/config.release.yml
opts into `delimited`. Without it Finch's release notes would come out empty,
which is the migration in miniature and worth seeing.
`commit_style` could only be set in configuration, which is the right home for
a project-wide convention but awkward for a single run — comparing versions
either side of a migration, most obviously. `--commit-style` overrides whatever
the configuration files resolved to.

It lands among the global options rather than on `compare` alone, since that is
where `AppRunner` already reads the values it builds the `App` from, and the
override is a property of the app's configuration rather than of one command.
`Configuration.applying(commitStyle:)` is the seam: a copy with the style
replaced, so the resolved configuration stays immutable.

The `ExpressibleByArgument` conformance sits in FinchApp, because FinchCore has
no business importing an argument parser. Notably it needs no `@retroactive` —
the compiler recognises both modules as this package's own, so the conformance
cannot be claimed from under us.

Also replaces `launchPath` and `launch()`, deprecated in favour of
`executableURL` and `run()`, which warned on every build. The availability
branch around them was already dead: it tested for macOS 10.13 against a
package which now declares 10.15, and swift-corelibs-foundation has had the
replacements for years, so the fallback only ever served Linux — where it was
the sole path, and the only one warning.
Carries the Swift 6.0 toolchain requirement and the commit style flip, both
breaking, so the minor rather than the patch.
@namolnad
namolnad merged commit d91bd33 into main Aug 14, 2026
3 checks passed
@namolnad
namolnad deleted the feature/conventional-commits branch August 14, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant