Conventional Commits
指导 Agent 将变更拆成可机器解析的提交头:类型、可选 scope、祈使句 subject,以及破坏性标记与正文脚注,便于 changelog、release 工具与 semver 规则一致落地。
SKILL 中应写清团队允许的类型集合、scope 命名约定(是否与包名 / 模块路径对齐)、以及「哪些类型参与版本 bump」——避免 Agent 把文档改动标成 feat。
与 PR 描述、变更日志生成等技能衔接:合并请求正文承载评审上下文,提交头保持对解析器友好;发行说明可由工具从规范提交聚合。
提交信息从草稿到落盘
[ 暂存区 / diff 摘要 ]
│
▼
┌─────────────────┐ 选 type;必要时填 scope;是否破坏性 → 决定是否加 !
│ 组装首行头 │──── 祈使语气、现在时;常见单行 ≤ 72 字符(含类型与 scope)
└─────────────────┘
│
▼
┌─────────────────┐ 解释「为什么」、实现细节、评审注意事项;与首行空一行
│ 正文(可选) │
└─────────────────┘
│
▼
┌─────────────────┐ BREAKING CHANGE: …;Closes #n;Reviewed-by:(团队约定)
│ 脚注(可选) │
└─────────────────┘
│
▼
[ git commit / hook / CI 校验通过 ]
机器可读的是「首行 + 脚注关键字」;正文仍建议写给人类。若仅用 ! 标示破坏性行为,须在正文或脚注中写清迁移说明,否则 release 工具可能无法生成完整 BREAKING 段落。
首行语法与长度
基本形态:type(scope)!: subject。其中 scope 与 ! 均可省略;冒号后必须有一个空格,再跟简短 subject。
type:小写,与团队约定列表一致;自定义类型也建议保持单一词根、避免空格。subject:不要用句号结尾;避免尾逗号;首字母小写(部分团队允许专有名词大写,需在 SKILL 写明)。- 超长首行会被终端与邮件列表截断,commitlint 常默认 72;Agent 生成时可主动换行正文承载细节。
常用类型与 scope
feat 与 fix 最常驱动用户可见的 minor / patch;其余如 docs、chore、ci、refactor、test、perf 是否触发版本号由工具配置决定,须在仓库文档中固定。
- Monorepo:scope 与包名或目录约定一致(如
feat(api):),便于按包过滤 changelog。 - 禁止滥用
feat描述仅格式化、注释或纯文档,除非团队明确把文档算作对外「特性」。 - 关联 issue:在脚注使用
Closes #123等,避免塞进 subject 造成噪声。
// commitlint 配置文件(.commitlintrc.json)
// 运行:npx commitlint --from HEAD~1 --to HEAD --verbose
{
"extends": ["@commitlint/config-conventional"],
"rules": {
// 允许的类型(自定义添加 wip、release)
"type-enum": [2, "always", [
"feat", // minor bump:新功能
"fix", // patch bump:缺陷修复
"perf", // patch bump:性能优化
"refactor", // no bump:重构(无行为变更)
"test", // no bump:测试
"docs", // no bump:文档
"style", // no bump:格式(不影响逻辑)
"chore", // no bump:构建/工具
"ci", // no bump:CI 配置
"build", // no bump:构建系统
"revert", // patch bump:回滚提交
"wip" // no bump:进行中(禁止合并 main)
]],
"subject-max-length": [2, "always", 72],
"subject-case": [2, "always", "lower-case"],
"body-leading-blank": [1, "always"],
"footer-leading-blank": [1, "always"]
}
}
破坏性变更(! 与脚注)
两种方式可并存:feat! 或 feat(api)!: 在首行标明破坏性;或在脚注单独写 BREAKING CHANGE: 段落。semantic-release 等工具通常识别其一即可,但迁移说明仍应写在脚注或正文。
# 破坏性变更的正确写法示例
# 方式 1:! 符号(推荐,首行即可见)
feat(api)!: change /users endpoint to return paginated response
Before: GET /users → User[]
After: GET /users → { items: User[], total: number, cursor: string }
BREAKING CHANGE: Response shape changed. Update all callers to use `.items`.
Migration: https://docs.example.com/migration/v2-users-api
Closes #1234
# 方式 2:仅脚注(! 省略,工具仍识别)
feat(auth): add OAuth 2.1 PKCE support
Implements RFC 7636 for public clients.
BREAKING CHANGE: Authorization endpoint now requires `code_challenge`.
Old flows without PKCE will receive 400 Bad Request after 2024-06-01.
Migration guide: https://wiki.example.com/oauth-pkce-migration
# 错误写法(勿学):
# feat: update user API and some breaking changes ← 含糊,工具无法识别
# chore!: update deps ← chore 标 ! 会误触发 major
Agent 起草时若不确定是否破坏公共 API,应在 SKILL 中要求标注「待维护者确认」或拆成独立提交,避免静默 major bump。
与 squash merge 协作
Squash 后历史只剩一条合并提交:应在 SKILL 中约定「最终 squash 消息」是否由 PR 标题 + 正文模板生成,以及是否保留 Conventional 首行供自动化解析。
- 若依赖 release-please 等从 PR 标题解析,标题本身应符合同一套 type(scope) 规则。
- 分支上多条小提交可在合并说明中折叠为一条用户向描述,避免 changelog 重复条目。
- 本地
commitlint与 pre-commit:在 SKILL 中附上典型错误输出与修复示例,便于 Agent 自纠。
# Husky pre-commit hook 配置
# 1. 安装 husky 与 commitlint
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
# 2. 初始化 husky
npx husky init
# 3. 创建 commit-msg hook(.husky/commit-msg)
cat > .husky/commit-msg << 'EOF'
#!/bin/sh
npx --no -- commitlint --edit "$1"
EOF
chmod +x .husky/commit-msg
# 4. 创建 pre-commit hook(.husky/pre-commit)
cat > .husky/pre-commit << 'EOF'
#!/bin/sh
npm run lint:staged # lint-staged:只 lint 暂存文件
npm test -- --passWithNoTests
EOF
# 5. package.json 配置 lint-staged
# "lint-staged": {
# "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
# "*.sql": ["sqlfluff fix --dialect postgres"]
# }
# 典型 commitlint 错误示例(便于 Agent 自纠):
# ✖ subject may not be empty [subject-empty]
# ✖ type must be one of [feat, fix, ...] [type-enum]
# ✖ subject must not be longer than 72 characters [subject-max-length]
提交头校验预览
下方表单用于拼装首行并做轻量校验(类型小写、scope 括号配对、冒号后空格、! 位置)。semver 提示为常见约定,实际 bump 以你仓库的 release 配置为准。
修改 type、scope、subject 或破坏性选项后,预览与校验结果会更新。
预览
---
name: conventional-commits
description: 提交类型规范、commitlint 配置、Husky hook 与破坏性变更写法
version: 2.0
---
# 提交格式
type(scope)!: subject <-- 72 字符以内,祈使句,无句号
正文(可选):空一行后写「为什么」与实现细节
BREAKING CHANGE: 具体说明变更内容与迁移步骤
Closes #issue-number
# 类型与版本 bump 规则
feat → MINOR(新功能,向后兼容)
fix/perf → PATCH(缺陷/性能,向后兼容)
feat!/fix! 或 BREAKING CHANGE 脚注 → MAJOR
docs/chore/ci/test/style/refactor/build → 不触发版本号
# commitlint 配置文件(.commitlintrc.json)
{"extends":["@commitlint/config-conventional"],
"rules":{"type-enum":[2,"always",["feat","fix","perf",
"refactor","test","docs","style","chore","ci","build","revert","wip"]],
"subject-max-length":[2,"always",72]}}
# Husky 安装(一次性)
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
npx husky init
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg