conventional-commit-message

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Conventional Commit Message

Conventional Commit提交信息

Use this skill when writing, amending, reviewing, or suggesting commit messages for repositories that use the
conventional-changelog-conventionalcommits
preset or compatible Conventional Commits release tooling.
当为使用
conventional-changelog-conventionalcommits
预设或兼容Conventional Commits发布工具的仓库编写、修改、审核或建议提交信息时,使用此技能。

Commit Shape

提交信息结构

Use the Conventional Commits 1.0.0 message structure:
text
<type>[optional scope][optional !]: <description>

[optional body]

[optional footer(s)]
Rules:
  • type
    is required and is followed by an optional parenthesized
    scope
    , an optional
    !
    , and
    : 
    .
  • description
    is required immediately after
    : 
    . Keep it concise, changelog-ready, and lower-case unless a proper noun or identifier requires casing. Do not end it with a period.
  • scope
    is optional and should name a section of the codebase.
  • A longer body is optional, starts one blank line after the description, and is free-form.
  • Footers are optional, start one blank line after the body or description, and each use either
    Token: value
    or
    Token #value
    .
  • Footer tokens use hyphens instead of spaces, such as
    Reviewed-by
    , except for
    BREAKING CHANGE
    .
    BREAKING-CHANGE
    is synonymous with
    BREAKING CHANGE
    .
  • Footer values may contain spaces and newlines. A footer value ends when the next valid footer token and separator is observed.
  • Conventional Commit units are not case-sensitive, except
    BREAKING CHANGE
    , which must be uppercase when used as that footer token.
  • Prefer one commit per coherent change. If one diff contains unrelated release visible changes, recommend splitting it.
  • Write the description so it reads well as a bullet in a generated changelog. Avoid vague implementation notes like
    add some feature
    ,
    update stuff
    , or
    fix issue
    . Prefer user-facing outcomes such as
    support custom scopes
    ,
    preserve prerelease tags
    , or
    require Node.js 22
    .
  • Wrap code identifiers in the description in backticks — function, option, flag, type, or file names:
    add `autocase` to match camelCase and kebab-case forms
    ,
    support `--no-*` negation
    . GitHub renders backticks in commit messages and generated changelogs preserve them, and they mark words like
    flag
    or
    rest
    as code where plain prose would be ambiguous.
  • Wrap any
    @
    -prefixed token that is not an intentional GitHub user mention in backticks — scoped package names (
    @types/node
    ), decorators (
    @Injectable
    ), JSDoc/annotation tags (
    @deprecated
    ). GitHub parses a bare
    @name
    in commit messages and changelogs as a user mention, which can link and notify an unrelated GitHub account.
  • Active verb phrases are usually better than passive sentences. Prefer
    support custom scopes
    over
    custom scopes were added
    unless passive voice is clearly more natural for the project.
Breaking changes are indicated by
!
immediately before
:
in the header or by a breaking-change footer. Use the footer when the impact is known:
text
feat(parser)!: remove legacy token fallback

BREAKING CHANGE: custom token fallbacks are no longer applied during parsing.
If
!
is used and no breaking-change footer is provided, the description is used to describe the breaking change.
采用Conventional Commits 1.0.0的消息结构:
text
<type>[可选 scope][可选 !]: <描述>

[可选正文]

[可选页脚]
规则:
  • type
    为必填项,后面可跟可选的括号包裹的
    scope
    、可选的
    !
    以及
    : 
  • 描述
    需紧跟在
    : 
    之后,为必填项。描述应简洁、适合生成变更日志,且除专有名词或标识符外均使用小写,结尾不要加句号。
  • scope
    为可选项,应指代代码库的某个模块。
  • 较长的正文为可选内容,需在描述后空一行开始,格式自由。
  • 页脚为可选内容,需在正文或描述后空一行开始,每条页脚采用
    Token: 值
    Token #值
    的格式。
  • 页脚标识使用连字符代替空格,例如
    Reviewed-by
    ,但
    BREAKING CHANGE
    除外。
    BREAKING-CHANGE
    BREAKING CHANGE
    同义。
  • 页脚的值可包含空格和换行符,当遇到下一个有效的页脚标识和分隔符时,当前页脚值结束。
  • Conventional Commit的单元不区分大小写,但
    BREAKING CHANGE
    作为页脚标识时必须大写。
  • 建议每个提交对应一个连贯的变更。如果一个差异包含不相关的、会体现在发布中的变更,建议拆分提交。
  • 描述的撰写要使其能作为生成的变更日志中的项目符号清晰呈现。避免模糊的实现说明,如
    add some feature
    update stuff
    fix issue
    。优先使用面向用户的成果描述,例如
    support custom scopes
    preserve prerelease tags
    require Node.js 22
  • 描述中的代码标识符(函数、选项、标志、类型或文件名)需用反引号包裹:
    add `autocase` to match camelCase and kebab-case forms
    support `--no-*` negation
    。GitHub会在提交信息中渲染反引号,生成的变更日志也会保留它们,这能将
    flag
    rest
    等词标记为代码,避免普通文本带来的歧义。
  • 所有非有意提及GitHub用户的
    @
    前缀标识需用反引号包裹——如作用域包名(
    @types/node
    )、装饰器(
    @Injectable
    )、JSDoc/注解标签(
    @deprecated
    )。GitHub会将提交信息和变更日志中的裸
    @name
    解析为用户提及,可能会链接并通知无关的GitHub账户。
  • 主动动词短语通常比被动句更好。优先使用
    support custom scopes
    而非
    custom scopes were added
    ,除非被动语态在项目中更自然。
破坏性变更可通过标题中
:
前的
!
或破坏性变更页脚来标识。当已知影响时,使用页脚:
text
feat(parser)!: remove legacy token fallback

BREAKING CHANGE: custom token fallbacks are no longer applied during parsing.
如果使用了
!
但未提供破坏性变更页脚,则描述将用于说明破坏性变更。

Type Selection

类型选择

Default visible types:
  • feat
    : user-facing feature; appears under
    Features
    ; bumps minor. Use this when a commit adds a new feature.
  • feature
    : accepted alias for
    feat
    ; prefer
    feat
    unless matching existing project style.
  • fix
    : bug fix or correctness fix; appears under
    Bug Fixes
    ; bumps patch. Use this when a commit represents a bug fix.
  • perf
    : performance improvement; appears under
    Performance Improvements
    ; bumps patch.
  • revert
    : revert commit; appears under
    Reverts
    ; bumps patch.
Default hidden types:
  • docs
    : documentation-only change.
  • style
    : formatting, whitespace, punctuation, or style-only change with no behavior change.
  • chore
    : general maintenance that should not be in changelog and should not bump, used only when no more specific hidden type fits.
  • refactor
    : code change that intentionally preserves behavior/API.
  • test
    : tests only.
  • build
    : build system, packaging, dependency metadata, generated build setup.
  • ci
    : CI workflow or automation-only change.
Treat hidden types as no-changelog and no-bump intent. If a code change clearly must not affect released behavior or API, use a hidden type instead of
fix
or
feat
, even when source files changed.
Types other than
feat
and
fix
are allowed by the Conventional Commits specification, but the spec gives them no implicit SemVer effect unless they include a breaking-change marker. This repository's preset assigns additional visibility and bump behavior through its configured commit types.
Do not use
chore
as a catch-all for every hidden change. Prefer the hidden type that describes the change most precisely:
build
for package manager, build tooling, dependency metadata, generated build setup, and workspace configuration;
ci
for CI-only automation;
test
for tests;
docs
for documentation;
refactor
for behavior-preserving code restructuring; and
style
for formatting-only changes.
Do not add
!
,
BREAKING CHANGE
, or
Release-As
to a hidden commit unless the release impact is intentional. Those signals override the hidden/no-bump intent.
Before choosing a hidden type, first ask whether the diff changes the public contract of any published package, even if the changed files are metadata, configuration, tests, generated files, or CI. Public contract includes supported runtime and platform versions, package exports, CLI behavior, config presets, peer dependency requirements, generated output, documented APIs, and default behavior. If the public contract becomes stricter, removes previously supported usage, or changes user-visible behavior, use a visible type. If previously supported usage no longer works, use
!
and a
BREAKING CHANGE
footer.
Common red flags for release-visible or breaking impact:
  • Raising minimum runtime, platform, browser, or engine support.
  • Removing, renaming, or narrowing package exports, entry points, CLI flags, options, config presets, or documented APIs.
  • Tightening peer dependency ranges or required external tools.
  • Changing generated output, defaults, validation rules, parsing behavior, or lint/config rules that consumers receive.
默认可见类型:
  • feat
    : 用户可见的功能;会出现在
    Features
    下;升级次版本号。当提交添加新功能时使用此类型。
  • feature
    :
    feat
    的可接受别名;除非匹配现有项目风格,否则优先使用
    feat
  • fix
    : 修复bug或正确性问题;会出现在
    Bug Fixes
    下;升级修订版本号。当提交代表bug修复时使用此类型。
  • perf
    : 性能优化;会出现在
    Performance Improvements
    下;升级修订版本号。
  • revert
    : 回滚提交;会出现在
    Reverts
    下;升级修订版本号。
默认隐藏类型:
  • docs
    : 仅文档变更。
  • style
    : 仅格式、空白字符、标点或风格变更,无行为变化。
  • chore
    : 常规维护,不应出现在变更日志中,也不应触发版本升级,仅当没有更具体的隐藏类型适用时使用。
  • refactor
    : 不改变行为/API的代码变更。
  • test
    : 仅测试相关变更。
  • build
    : 构建系统、打包、依赖元数据、生成的构建配置相关变更。
  • ci
    : 仅CI工作流或自动化相关变更。
隐藏类型意味着不生成变更日志且不触发版本升级。如果代码变更明确不会影响已发布的行为或API,即使修改了源文件,也应使用隐藏类型而非
fix
feat
Conventional Commits规范允许
feat
fix
之外的类型,但规范未赋予它们隐式的SemVer影响,除非包含破坏性变更标记。本仓库的预设通过配置的提交类型赋予了额外的可见性和版本升级行为。
不要将
chore
作为所有隐藏变更的万能类型。优先选择最能准确描述变更的隐藏类型:
build
用于包管理器、构建工具、依赖元数据、生成的构建配置和工作区配置;
ci
用于仅CI自动化;
test
用于测试;
docs
用于文档;
refactor
用于保持行为不变的代码重构;
style
用于仅格式变更。
除非有意产生发布影响,否则不要在隐藏提交中添加
!
BREAKING CHANGE
Release-As
。这些信号会覆盖隐藏/不升级版本的意图。
选择隐藏类型前,首先要判断差异是否改变了任何已发布包的公共契约,即使修改的是元数据、配置、测试、生成文件或CI相关文件。公共契约包括支持的运行时和平台版本、包导出、CLI行为、配置预设、对等依赖要求、生成输出、文档化API和默认行为。如果公共契约变得更严格、移除了之前支持的用法或改变了用户可见的行为,使用可见类型。如果之前支持的用法不再可用,使用
!
BREAKING CHANGE
页脚。
发布可见或破坏性影响的常见警示信号:
  • 提高最低运行时、平台、浏览器或引擎版本要求。
  • 移除、重命名或缩小包导出、入口点、CLI标志、选项、配置预设或文档化API。
  • 收紧对等依赖范围或要求外部工具。
  • 改变生成输出、默认值、验证规则、解析行为或消费者收到的lint/配置规则。

Impact Heuristics

影响判断

Choose the type by release impact, not by file path:
  • Public behavior added:
    feat
    .
  • Public behavior corrected:
    fix
    .
  • Runtime speed/memory improvement without behavior change:
    perf
    .
  • Runtime dependency updates in
    dependencies
    :
    fix
    . They change the published package dependency graph, can improve correctness/security/runtime behavior, and should trigger a release even when source code is untouched. Use
    !
    only if the dependency update changes or removes supported public behavior.
  • Public contract made stricter or previously supported usage removed:
    feat!
    or
    fix!
    with a
    BREAKING CHANGE
    footer. Example: raising
    engines.node
    to Node.js 22 should use a subject like
    require Node.js 22
    .
  • Internal rewrite with preserved behavior:
    refactor
    .
  • Dependency lockfile/package-manager maintenance, build tooling, generated build setup, or workspace package-manager configuration with no shipped behavior change:
    build
    , unless local style strongly prefers another hidden type.
  • CI-only workflow edits:
    ci
    .
  • Tests added or adjusted without product code impact:
    test
    .
  • Docs/readme/examples prose only:
    docs
    .
  • General housekeeping that does not fit the more specific hidden types:
    chore
    .
When unsure whether a source-code change is user-visible, inspect tests and public API usage. If the change is defensive cleanup, migration plumbing, renaming private helpers, lint fixes, or type-only restructuring, prefer a hidden type.
根据发布影响选择类型,而非文件路径:
  • 添加了公共行为:
    feat
  • 修正了公共行为:
    fix
  • 运行速度/内存优化且无行为变化:
    perf
  • dependencies
    中的运行时依赖更新:
    fix
    。这些变更会改变已发布包的依赖关系图,可提升正确性/安全性/运行时行为,即使未修改源代码也应触发发布。仅当依赖更新改变或移除了支持的公共行为时使用
    !
  • 公共契约变得更严格或移除了之前支持的用法:
    feat!
    fix!
    并附带
    BREAKING CHANGE
    页脚。示例:将
    engines.node
    提升至Node.js 22应使用类似
    require Node.js 22
    的主题。
  • 内部重构且保持行为不变:
    refactor
  • 依赖锁文件/包管理器维护、构建工具、生成的构建配置或工作区包管理器配置且无发布行为变化:
    build
    ,除非本地风格强烈偏好其他隐藏类型。
  • 仅CI工作流编辑:
    ci
  • 添加或调整测试且无产品代码影响:
    test
  • 仅文档/自述文件/示例文本变更:
    docs
  • 不适合更具体隐藏类型的常规内务处理:
    chore
当不确定源代码变更是否对用户可见时,检查测试和公共API使用情况。如果变更是防御性清理、迁移管道、重命名私有辅助函数、lint修复或仅类型重构,优先使用隐藏类型。

Scope Selection

范围选择

Before choosing a scope, inspect the repository:
bash
git diff --staged --name-only
git diff --name-only
rg --files -g '*commitlint*' -g '.commitlintrc*' -g 'commitlint.config.*'
rg --files -g 'package.json'
Use staged changes first. If nothing is staged, use the working tree diff.
If the project uses commitlint, read its config before choosing a message. The config may define allowed types, allowed scopes, required casing, max lengths, and other local formatting rules. Common locations include
.commitlintrc
,
.commitlintrc.json
,
.commitlintrc.yaml
,
.commitlintrc.yml
,
.commitlintrc.js
,
.commitlintrc.cjs
,
.commitlintrc.mjs
,
.commitlintrc.ts
,
commitlint.config.js
,
commitlint.config.cjs
,
commitlint.config.mjs
, and
commitlint.config.ts
.
Single-repo projects:
  • Usually omit the scope.
  • Use a scope only when the project already has clear scopes such as
    cli
    ,
    docs
    ,
    deps
    ,
    release
    , or subsystem names.
Monorepos:
  • Prefer the affected package name as scope.
  • Derive package scopes from workspace package names, existing commitlint
    scope-enum
    , or recent commits.
  • If commitlint or local tooling supports multiple scopes and one coherent release-visible change affects several published packages, use all affected package scopes instead of collapsing the change to one package.
  • Prefer the repository's accepted multiple-scope separator. If local config enables multiple scopes but does not make the separator obvious, validate a comma-separated scope list with commitlint before using it.
  • If package names are npm-scoped, follow the repo's existing convention: either the full package name or the unscoped package segment.
  • If one package changed, use that package scope.
  • If several packages changed for one shared reason, use a shared scope such as
    deps
    ,
    dev-deps
    , or
    release
    when that scope adds useful meaning.
  • For monorepo-wide release-impacting changes that touch many packages, prefer omitting the scope unless the repo requires one. Release tooling usually uses the changed packages, not a
    global
    scope, to decide which packages are affected.
  • If several packages changed for different reasons, first try to describe the common intent honestly. If the only honest message would be vague or hide release impact, then suggest separate commits.
For dependency-only work:
  • Use
    fix(deps)
    for runtime dependency updates in
    dependencies
    when
    deps
    is an accepted scope.
  • Use
    dev-deps
    for development-only tooling dependency updates when that is an accepted scope.
选择范围前,先检查仓库:
bash
git diff --staged --name-only
git diff --name-only
rg --files -g '*commitlint*' -g '.commitlintrc*' -g 'commitlint.config.*'
rg --files -g 'package.json'
优先使用暂存的变更。如果没有暂存变更,使用工作区差异。
如果项目使用commitlint,选择消息前先阅读其配置。配置可能定义了允许的类型、允许的范围、必填大小写、最大长度和其他本地格式化规则。常见位置包括
.commitlintrc
.commitlintrc.json
.commitlintrc.yaml
.commitlintrc.yml
.commitlintrc.js
.commitlintrc.cjs
.commitlintrc.mjs
.commitlintrc.ts
commitlint.config.js
commitlint.config.cjs
commitlint.config.mjs
commitlint.config.ts
单仓库项目:
  • 通常省略范围。
  • 仅当项目已有明确范围(如
    cli
    docs
    deps
    release
    或子系统名称)时使用范围。
单体仓库(Monorepos):
  • 优先使用受影响的包名作为范围。
  • 从工作区包名、现有commitlint
    scope-enum
    或最近的提交中推导包范围。
  • 如果commitlint或本地工具支持多个范围,且一个连贯的发布可见变更影响多个已发布包,则使用所有受影响的包范围,而非将变更合并到一个包。
  • 优先使用仓库接受的多范围分隔符。如果本地配置启用了多范围但未明确分隔符,使用commitlint验证逗号分隔的范围列表后再使用。
  • 如果包名是npm作用域的,遵循仓库的现有约定:使用完整包名或非作用域的包段。
  • 如果只有一个包变更,使用该包范围。
  • 如果多个包因共同原因变更,当
    deps
    dev-deps
    release
    等共享范围能增加有用信息时,使用该共享范围。
  • 对于影响多个包的单体仓库级发布变更,除非仓库要求,否则优先省略范围。发布工具通常使用变更的包而非
    global
    范围来决定受影响的包。
  • 如果多个包因不同原因变更,首先尝试诚实地描述共同意图。如果唯一诚实的消息会模糊或隐藏发布影响,则建议拆分提交。
仅依赖相关工作:
  • deps
    是可接受范围时,使用
    fix(deps)
    更新
    dependencies
    中的运行时依赖。
  • dev-deps
    是可接受范围时,使用它更新仅开发工具的依赖。

Message Construction Workflow

消息构建流程

  1. Inspect changed files and diff summary.
  2. Identify the released behavior impact.
  3. Choose visible vs hidden type.
  4. Choose the narrowest valid scope.
  5. Write the subject as a changelog bullet from the user's perspective when visible, or from the maintenance intent when hidden.
  6. Add body/footer only when needed for breaking changes, issue closure, release-as, or important context.
  7. If the work is explicitly tied to an issue, PR, bug report, or user-provided tracker URL, include the relevant reference in the commit body/footer. For fixes that should close the issue, use a closing footer such as
    Fixes #123.
    . For non-closing context, use a non-closing reference such as
    Refs #123.
    .
  8. If commitlint is configured and the CLI is available, validate the candidate before presenting it to the user.
Validate with the local installed commitlint binary first when possible. Prefer
./node_modules/.bin/commitlint
over package-manager runners because it avoids package manager environment, sandbox, and registry behavior while still using the repository's installed version and config.
bash
echo 'type(scope): subject' | ./node_modules/.bin/commitlint
Use package-manager runners only as fallback when the local binary is missing and running them is acceptable in the current environment:
bash
echo 'type(scope): subject' | pnpm commitlint
echo 'type(scope): subject' | npm exec commitlint
echo 'type(scope): subject' | npx commitlint
Use the actual candidate message in place of the example. For multi-line messages, use
printf
or another command that preserves the full message exactly:
bash
printf '%s\n\n%s\n' 'feat(parser)!: remove legacy token fallback' 'BREAKING CHANGE: custom token fallbacks are no longer applied.' | ./node_modules/.bin/commitlint
If commitlint fails, adjust the type, scope, case, or subject length and validate again. If commitlint is configured but the CLI cannot be run in the current environment, tell the user.
Use issue references naturally:
text
fix(parser): handle empty commit body refs #123
Use closing keywords only when the commit should close an issue:
text
fix(cli): preserve prerelease tag when appending changelog

Fixes #123.
For coherent changes affecting multiple packages in a monorepo, use multiple scopes when supported by the repository:
text
fix(package-a,package-b): preserve compare links

Fixes #123.
  1. 检查变更文件和差异摘要。
  2. 确定发布行为影响。
  3. 选择可见或隐藏类型。
  4. 选择最窄的有效范围。
  5. 可见类型的主题从用户视角编写为变更日志项目符号,隐藏类型的主题从维护意图编写。
  6. 仅在需要说明破坏性变更、关闭问题、指定发布版本或提供重要上下文时添加正文/页脚。
  7. 如果工作明确关联到问题、PR、bug报告或用户提供的跟踪器URL,在提交正文/页脚中包含相关引用。对于应关闭问题的修复,使用类似
    Fixes #123.
    的关闭页脚。对于非关闭上下文,使用类似
    Refs #123.
    的非关闭引用。
  8. 如果配置了commitlint且CLI可用,在呈现给用户前验证候选消息。
尽可能优先使用本地安装的commitlint二进制文件。优先使用
./node_modules/.bin/commitlint
而非包管理器运行器,因为它避免了包管理器环境、沙箱和注册表行为,同时仍使用仓库的已安装版本和配置。
bash
echo 'type(scope): subject' | ./node_modules/.bin/commitlint
仅当本地二进制文件缺失且当前环境允许运行时,才使用包管理器运行器作为备选:
bash
echo 'type(scope): subject' | pnpm commitlint
echo 'type(scope): subject' | npm exec commitlint
echo 'type(scope): subject' | npx commitlint
用实际候选消息替换示例。对于多行消息,使用
printf
或其他能准确保留完整消息的命令:
bash
printf '%s\n\n%s\n' 'feat(parser)!: remove legacy token fallback' 'BREAKING CHANGE: custom token fallbacks are no longer applied.' | ./node_modules/.bin/commitlint
如果commitlint验证失败,调整类型、范围、大小写或主题长度并重新验证。如果配置了commitlint但当前环境无法运行CLI,告知用户。
自然使用问题引用:
text
fix(parser): handle empty commit body refs #123
仅当提交应关闭问题时使用关闭关键字:
text
fix(cli): preserve prerelease tag when appending changelog

Fixes #123.
对于单体仓库中影响多个包的连贯变更,当仓库支持时使用多个范围:
text
fix(package-a,package-b): preserve compare links

Fixes #123.

Examples

示例

Visible release entries:
text
feat(writer): support scoped package links
feat!: require Node.js 22
feat: add `rest` function to read all remaining arguments
fix(parser): keep issue references from multiline footers
fix(cli): handle `--no-*` flags in strict mode
perf(git-client): avoid duplicate tag lookups
revert: feat(writer): support scoped package links
Hidden no-bump entries:
text
refactor(conventional-changelog): simplify context defaults
test(writer): cover empty reference groups
docs(conventionalcommits): clarify hidden commit types
build(dev-deps): update oxlint
ci: use node 22 in tests
chore(release): prepare package metadata
Monorepo package scopes:
text
fix(conventional-changelog-writer): preserve reference order
refactor(conventional-commits-parser): split token normalization
test(git-client): cover lightweight tags
可见发布条目:
text
feat(writer): support scoped package links
feat!: require Node.js 22
feat: add `rest` function to read all remaining arguments
fix(parser): keep issue references from multiline footers
fix(cli): handle `--no-*` flags in strict mode
perf(git-client): avoid duplicate tag lookups
revert: feat(writer): support scoped package links
隐藏无升级条目:
text
refactor(conventional-changelog): simplify context defaults
test(writer): cover empty reference groups
docs(conventionalcommits): clarify hidden commit types
build(dev-deps): update oxlint
ci: use node 22 in tests
chore(release): prepare package metadata
单体仓库包范围:
text
fix(conventional-changelog-writer): preserve reference order
refactor(conventional-commits-parser): split token normalization
test(git-client): cover lightweight tags

Output Style

输出风格

When the user asks for a commit message, output only the recommended message unless they ask for explanation or alternatives. For uncertain impact, provide two concise options and explain the release consequence:
text
fix(parser): handle empty commit body
or, if no release bump is intended:
text
refactor(parser): handle empty commit body defensively
当用户请求提交信息时,仅输出推荐的消息,除非用户要求解释或备选方案。对于不确定的影响,提供两个简洁选项并说明发布后果:
text
fix(parser): handle empty commit body
或者,如果无意触发版本升级:
text
refactor(parser): handle empty commit body defensively