名称: git 描述: 使用约定式提交的Git提交和拉取请求指南。适用于创建提交、编写提交消息、创建PR或审查PR描述时。
Git提交与拉取请求指南
约定式提交格式
<类型>[可选范围]: <描述>
[可选正文]
[可选页脚]
提交类型
feat: 新功能(与语义版本中的MINOR对应)fix: 错误修复(与语义版本中的PATCH对应)docs: 仅文档更改refactor: 既不修复错误也不添加功能的代码更改perf: 性能改进test: 添加或修改测试chore: 维护任务、依赖更新等style: 代码风格更改(格式化、缺少分号等)build: 构建系统或依赖项更改ci: CI配置文件和脚本更改
范围指南
- 范围是可选的:仅在提供清晰度时添加
- 使用小写,放在类型后的括号中:
feat(转录): - 优先使用特定组件/模块名称,而非通用术语
- 您当前的做法很好:组件名称(
EditRecordingDialog)、功能区域(transcription、sound) - 避免过于通用的范围,如
ui或backend,除非确实合适
何时使用范围
- 当更改局限于特定组件/模块时
- 当有助于区分类似更改时
- 当在具有不同区域的大型代码库中工作时
何时不使用范围
- 当更改同等影响多个区域时
- 当类型本身足够描述性时
- 对于小而明显的更改
描述规则
- 冒号和空格后立即以小写开头
- 使用祈使语气(“添加”而非“已添加”或“添加”)
- 结尾不加句号
- 第一行保持在50-72个字符以内
破坏性更改
- 在类型/范围后添加
!,冒号前:feat(api)!: 更改端点结构 - 在页脚中包含
BREAKING CHANGE:及详细信息 - 这些触发语义版本中的MAJOR版本升级
遵循您风格的示例:
feat(transcription): 为OpenAI提供者添加模型选择fix(sound): 解决资产模块中的音频导入路径refactor(EditRecordingDialog): 实现工作副本模式docs(README): 澄清成本比较部分chore: 更新依赖到最新版本fix!: 更改默认转录API端点
提交消息最佳实践
“为什么”比“什么”更重要
提交消息主题行描述了什么更改。提交正文解释了为什么。
好的提交(解释动机):
fix(auth): 防止文件上传期间会话超时
用户在上传大文件时会在中途被登出,因为会话刷新仅在导航时触发,而不是后台活动时。
差的提交(仅描述什么):
fix(auth): 添加上传处理程序的保活调用
第一个提交告诉未来开发者为什么代码存在。第二个让他们深入代码以理解目的。
其他最佳实践
- 永远不要包含Claude Code或opencode水印或归属
- 每个提交应代表一个单一、原子性的更改
- 为未来开发者(包括您自己)编写提交
- 如果需要多行描述您做了什么,考虑拆分提交
拉取请求指南
动机优先
每个PR描述必须从为什么存在此更改开始。不是更改了什么文件,不是如何工作——为什么。
好的PR开头:
用户在上传大文件时会在中途被登出。会话刷新仅在导航时触发,而不是在上传等后台活动期间。
差的PR开头:
此PR添加上传处理程序的保活调用并更新会话刷新逻辑。
读者应在看到解决方案前理解问题。
API更改必须包含代码示例
如果PR引入或修改API,您必须包含显示如何使用的代码示例。没有例外。
需要代码示例的情况:
- 新函数、类型或导出
- 函数签名更改
- 新CLI命令或标志
- 新HTTP端点
- 配置更改
好的API PR(显示实际使用):
// 定义操作一次
const actions = {
posts: {
create: defineMutation({
input: type({ title: 'string' }),
handler: ({ title }) => client.tables.posts.create({ title }),
}),
},
};
// 传递给适配器 - 它们生成CLI命令和HTTP路由
const cli = createCLI(client, { actions });
const server = createServer(client, { actions });
差的API PR(仅描述而不显示):
此PR添加一个从操作定义生成CLI命令和HTTP路由的操作系统。
第一个版本让审阅者一目了然地理解API。第二个迫使他们深入代码以理解调用点。
重构时使用前后代码片段
代码示例不仅适用于API更改。对于不改变公共API但改变代码结构的内部重构,前后代码片段具体展示改进:
// 之前:直接使用YKeyValueLww并手动扫描
const ykv = new YKeyValueLww<unknown>(yarray);
function reconstructRow(rowId) { // O(n) - 扫描每个单元格
for (const [key, entry] of ykv.map) {
if (key.startsWith(prefix)) { ... }
}
}
// 之后:组合的存储层
const cellStore = createCellStore<unknown>(ydoc, TableKey(tableId));
const rowStore = createRowStore(cellStore);
rowStore.has(id) // O(1)
rowStore.get(id) // O(m) 其中 m = 每行字段数
rowStore.count() // O(1)
在以下情况使用前后片段:
- 内部实现发生重大变化,即使外部API未变
- 性能特征改变,代码显示原因
- 复杂度被移动/分解(显示内联内容与现在委托的内容)
使用ASCII艺术进行视觉沟通
广泛使用ASCII图表沟通复杂想法。它们比散文更易扫描,并一目了然地显示关系。
旅程/演进图
对于迭代先前工作的PR,显示演进:
┌─────────────────────────────────────────────────────────────────────────┐
│ PR #1217 (1月7日) │
│ "添加YKeyValue以实现1935倍存储改进" │
│ │
│ Y.Map (524,985字节) ──→ YKeyValue (271字节) │
│ │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ PR #1226 (1月8日) │
│ "移除YKeyValue,使用原生Y.Map + 时代压缩" │
│ │
│ 理由:"不可预测的LWW行为" ← ⚠️ (误导性!) │
│ │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 此PR │
│ "恢复带LWW时间戳的YKeyValue" │
│ │
│ 原因:基于时间戳的解析给出直观的"最新胜出" │
│ │
└─────────────────────────────────────────────────────────────────────────┘
分层架构图
显示组件如何堆叠:
┌─────────────────────────────────────────────────────────────┐
│ defineWorkspace() + workspace.create() │ ← 高级
│ 内部创建Y.Doc,绑定表/kv/能力 │
├─────────────────────────────────────────────────────────────┤
│ createTables(ydoc, {...}) / createKv(ydoc, {...}) │ ← 中级
│ 绑定到现有Y.Doc │
├─────────────────────────────────────────────────────────────┤
│ defineTable() / defineKv() │ ← 低级
│ 纯模式定义 │
└─────────────────────────────────────────────────────────────┘
比较表
显示方法之间的权衡:
┌────────────────────────────────────────────────────────────────┐
│ 使用场景 │ 推荐方法 │
├───────────────────────────────────┼────────────────────────────┤
│ 实时协作,简单情况 │ YKeyValue (位置性) │
│ 离线优先,多设备 │ YKeyValueLww (时间戳) │
│ 时钟同步不可靠 │ YKeyValue (不依赖时钟) │
└────────────────────────────────────────────────────────────────┘
流程图
显示数据/控制流:
┌────────────────────────────────────────────────────────────────┐
│ 冲突解析 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 客户端A (下午2:00) ──┐ │
│ │──→ 同步 ──→ 胜者? │
│ 客户端B (下午3:00) ──┘ │
│ │ │
│ ┌────────────────┴────────────────┐ │
│ ▼ ▼ │
│ YKeyValue YKeyValueLww │
│ (客户端ID胜出) (时间戳胜出) │
│ ~50% 正确率 100% 正确率 │
│ │
└────────────────────────────────────────────────────────────────┘
组合树图
对于改变模块组合方式的重构,使用轻量级缩进树符号而不是重箱线图。这能一目了然地显示依赖/组合层次:
之前 — 一个模块做所有事情:
TableHelper (模式 + CRUD + 行重建 + 观察者)
└── YKeyValueLww ← Map<"rowId:colId", 条目>
├── reconstructRow() O(n) 扫描所有键寻找前缀
├── collectRows() O(n) 按rowId分组所有单元格
└── deleteRowCells() O(n) 过滤 + 删除
之后 — 每个层有单一职责:
TableHelper (模式验证、类型化CRUD、品牌化ID类型)
└── RowStore (内存行索引 → O(1) has/count, O(m) get/delete)
└── CellStore (单元格语义:键解析、类型化更改事件)
└── YKeyValueLww (通用LWW冲突解析原语)
组合树的关键属性:
- 对于单子节点使用
└──,存在兄弟节点时使用├── - 用括号注释每个节点的职责
- 当重构改变性能特征时显示
- 前后对比使改进立即可见
文件重定位树
当重构物理移动文件且重定位本身就是架构声明时,将移动模式显示为树。这不是“列出更改的文件”(技能禁止)——它显示结构重组:
packages/epicenter/src/
├── shared/
│ ├── y-cell-store.ts → dynamic/tables/y-cell-store.ts
│ └── y-row-store.ts → dynamic/tables/y-row-store.ts
└── dynamic/tables/
└── table-helper.ts (重构以组合上述内容)
在以下情况使用文件重定位树:
- 作为模块边界更改的一部分,文件在目录之间移动
- 新位置传达架构意图(例如,“这些属于表子系统,不是共享的”)
- 移动了2-6个文件;更多时,描述模式
不要在以下情况使用:
- 文件重命名但停留在同一目录
- 移动是真实更改的附带事件
何时使用图表
- 旅程图:PR迭代先前工作或修复过去决策
- 分层图:PR引入或改变具有不同级别的架构
- 组合树:PR重构模块如何组合或委托给彼此
- 文件重定位树:PR作为架构声明在目录之间移动文件
- 比较表:PR引入替代方案或解释权衡
- 流程图:PR改变数据或控制如何在组件之间移动
ASCII艺术字符使用:┌ ┐ └ ┘ ─ │ ├ ┤ ┬ ┴ ┼ ▼ ▲ ◀ ▶ ──→ ←── ⚠️ ✅ ❌
交错散文和视觉
不要让散文连续超过短段落而没有视觉中断。节奏应为:上下文 → 视觉 → 解释 → 视觉 → …
每个视觉(代码片段、ASCII图表、前后块)前应有1-3句上下文,并可选择后跟一句解释细微细节。如果您连续写超过4-5句散文,就错过了使用图表或代码块的机会。
良好节奏 — 散文和视觉自然交替:
[1-2句:问题是什么及其重要性]
\`\`\`typescript
// 显示新API的代码示例
workspace.extensions.sync.reconnect(directAuth('https://cloud.example.com'));
\`\`\`
\`\`\`
┌────────────────────────────┐
│ 流程图显示步骤 │──► 逐步发生了什么
│ 如何连接 │
└────────────────────────────┘
\`\`\`
[1-2句:解释细微实现细节]
\`\`\`typescript
// 前后显示修复
\`\`\`
[1句:为什么之前有缺陷]
\`\`\`
┌──────────────────────────────────┐
│ 架构图显示受影响部分 │
└──────────────────────────────────┘
\`\`\`
差节奏 — 长文本墙,视觉附加在末尾:
[段落解释问题]
[段落解释解决方案]
[段落解释实现细节]
[段落解释另一细节]
\`\`\`
[底部单图]
\`\`\`
读者眼睛应在散文和视觉之间跳跃。散文提供“为什么”,视觉提供“什么”和“如何”。两者都不应长期主导。
其他指南
- 永远不要在PR标题/描述中包含Claude Code或opencode水印或归属
- PR标题应遵循与提交相同的约定式提交格式
- 专注于更改的“为什么”和“什么”,而不是“如何创建”
- 突出显示任何破坏性更改
- 链接到相关问题
验证GitHub用户名
关键:在PR描述、问题评论或任何GitHub内容中提及GitHub用户时,使用@用户名,永远不要猜测或假设用户名。始终使用GitHub CLI编程验证:
# 获取PR作者
gh pr view <PR编号> --json author
# 获取问题作者
gh issue view <问题编号> --json author
这避免了您错误赞扬他人的尴尬错误。在编写@提及前,始终运行验证命令。
合并策略
合并PR时,使用常规合并提交(非压缩):
gh pr merge --merge # 正确:保留提交历史
# 错误:gh pr merge --squash
# 错误:gh pr merge --rebase
# 如果需要绕过分支保护,使用--admin标志
gh pr merge --merge --admin
保留个别提交;它们讲述工作如何演进的故事。
拉取请求正文格式
简单PR(单一目的更改)
使用干净段落格式:
第一段:解释更改做什么以及解决什么问题。
后续段落:解释实现如何工作。
示例:
此更改为抽屉组件在内容超过可用抽屉高度时启用适当的垂直滚动。之前,长内容的抽屉可能会溢出而没有适当的滚动行为,使用户难以访问所有内容,并导致移动UX不佳。
为此,我将`{@render children?.()}`包装在`<div class="flex-1 overflow-y-auto">`容器中。`flex-1`类确保内容区域占据顶部固定拖动手柄后的所有剩余空间,而`overflow-y-auto`在内容高度超过可用空间时启用垂直滚动。
架构PR(API更改、结构重构)
对于更改API、存储结构或架构模式的PR,使用此章节顺序:
-
介绍段落(1-2):更改做什么以及为什么存在。如果适用,包括破坏性更改通知。以一句话决策摘要结尾:“我们选择X而不是Y,因为Z。”
-
API迁移:显示新用法的前后代码示例。任何API更改必须包含。
-
存储/数据结构:显示任何结构更改前后布局的ASCII图表。
-
技术细节:扩展点、类型定义、配置格式——带代码示例。
-
理由:为什么选择此方法。旧方法设计为什么,为什么在实践中不行,新方法启用什么。
-
未来工作:什么可以稍后重新添加,什么被故意推迟。
-
(可选)更改摘要/测试计划:如果包含,保持最小化并放在最后。
关键原则:
- 视觉优先:每个章节应以代码或ASCII图表领先;散文解释视觉
- 代码片段和ASCII艺术最易扫描——突出它们
- 理由仅散文(无需视觉);它解释思考
- 完全跳过“更改”章节,或使其最小化在末尾
何时使用架构格式:
- 公共API形状更改(导出、签名、配置格式)
- 持久数据格式更改(存储布局、迁移)
- 跨包合同更改
- 新子系统或重大重构
何时使用简单格式:
- 本地化修复/功能,无需消费者迁移
- 行为保留的内部重构
语气和口吻
- 对话但精确:像向同事解释一样写作
- 直接诚实:“这很痛苦”而不是“这提出了挑战”
- 展示您的思考:“我们考虑了X,但Y更有意义,因为…”
- 团队决策用“我们”,个人观察用“我”
示例PR描述:
这修复了状态管理中嵌套响应性的长期问题。
首先,一些上下文:用户一直发现创建深度响应状态很繁琐。当前方法需要手动get/set属性,感觉不够Svelte-like。同时,我们想为了未来的性能优化远离对象突变,但`obj = { ...obj, x: obj.x + 1 }`很丑并产生开销。
此PR引入基于代理的响应性,让您编写惯用JavaScript:
```javascript
let todos = $state([]);
todos.push({ done: false, text: 'Learn Svelte' }); // 直接工作
```
底层,我们使用代理懒创建信号。这给我们突变的工效学与不可变的性能优势。
仍然TODO:
- 大型数组性能优化
- 文档更新
- 现有代码库迁移指南
这加倍了Svelte的编写更少、更直观代码的哲学,同时为计划在v6的细粒度响应性改进奠定基础。
要避免什么
- 列出更改的文件:永远不要枚举哪些文件被修改。GitHub的“已更改文件”标签已显示此;PR描述应解释为什么,而不是什么文件
- 顶部的“更改”章节:如果您需要更改摘要,放在最后并保持最小化。大多数PR不需要。
- 测试计划:除非特别请求,跳过。测试应在代码中,而不是散文描述。
- 要点或结构化列表(在简单PR中)
- 章节标题如“## 摘要”或“## 所做更改”
- 营销语言或过多格式
- 企业语言:“此PR通过利用…增强我们的解决方案”
- 营销话术:“游戏改变”、“革命性”、“无缝”
- 戏剧性夸张:“感觉像永恒”、“痛点”、“折磨人”——坚持事实(“节省150ms”)而非戏剧
- 过度解释简单更改
- 为合理决策道歉语气
不要包含什么:
Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>- 任何AI辅助引用
Generated with [opencode](https://opencode.ai)Co-Authored-By: opencode <noreply@opencode.ai>- 工具归属或水印