| name | postmortem |
| description | Analyze fix commits between a git tag and HEAD, review existing postmortem topics, select representative incidents, and generate curated postmortem reports in ./postmortem/. Triggers: postmortem, generate postmortem, incident report, post-mortem analysis, RCA, root cause analysis, analyze fixes, what broke since release, /postmortem <tag>
|
Postmortem 技能
分析目标 git tag 与当前 HEAD 之间所有 fix: / fix(...): commits,结合现有的 postmortem 知识库,筛选出值得沉淀的代表性事故,生成或追加专业详尽的复盘报告到 ./postmortem/ 中。
报告按话题(topic)组织为文件,而非按时间戳。同一类问题(如"GDB 注入超时")无论跨多少次 release 都归到同一个文件里。文件内部按时间和 commit 分为多个事故组块,每个组块记录一次具体事故实例的严重级别、根因分析、引发事故的提交、复现步骤、修复详情、经验教训和预防措施。
重要原则:发现所有 fix,但不必把所有 fix 都写成事故。Postmortem 是知识库,不是提交流水账。每次写入前必须 review 目标 topic 文件,判断新增内容是否带来新的根因、风险模式、预防措施或代表性案例;重复、低影响、纯测试修补、同根因 follow-up fix 可以合并、引用或跳过。
快速参考
/postmortem v0.1.5
/postmortem latest
/postmortem v0.1.4 v0.1.6
前置条件
- Git 仓库使用约定式提交消息(
fix:、fix(scope):、feat: 等)
- 目标 tag 必须存在 — 通过
git tag --list 验证
- 工作目录干净或已提交 — postmortem 生成不应与未提交的改动混在一起
工作流概览
阶段 1:发现并解析 Fix 提交
↓
阶段 2:按话题分组提交
↓
阶段 3:候选评审与代表性筛选
↓
阶段 4:分析入选事故组块(根因、严重级别、复现、教训)
↓
阶段 5:生成或追加 Postmortem 报告
↓
阶段 6:更新汇总索引
阶段 1:发现并解析 Fix 提交
1.1 解析 Tag 范围
用户提供单个 tag(如 v0.1.5):
用户输入 latest:
- 查找最近的 tag:
git describe --tags --abbrev=0
- 范围:
<latest_tag>..HEAD
用户提供两个 tag(如 v0.1.4 v0.1.6):
1.2 提取 Fix 提交
git log <start_tag>..<end_ref> \
--grep="^fix" \
--pretty=format:"%H|%h|%an|%ae|%ai|%s|%b" \
--no-merges
将每个提交解析为结构化数据:
- hash:完整 SHA
- short_hash:缩写 SHA
- author:作者姓名
- email:作者邮箱
- date:ISO 8601 时间戳
- subject:提交消息第一行
- body:提交消息剩余部分(可能包含 issue 引用、详细信息)
1.3 解析约定式提交格式
对每个 fix 提交的 subject 进行提取:
fix(scope): description
^ ^ ^
| | └── 修复了什么
| └──────────── 受影响的模块/组件
└──────────────── 提交类型(此处始终为 "fix")
同时从 subject + body 中提取:
- Issue 引用:
#123、Fixes #456、Closes #789
- 破坏性变更:body 中的
BREAKING CHANGE: 或 scope 后的 !
1.4 查找相关的非 Fix 提交(上下文)
对每个 fix 提交,搜索可能的致因提交 — 即引入该 bug 的提交:
git log <start_tag>..<fix_commit>^ -- <files_changed_by_fix> \
--pretty=format:"%h|%an|%ai|%s" \
--no-merges
同时通过 git blame 检查修复所改动的具体行,以定位引入 bug 的提交:
git diff <fix_commit>^..<fix_commit> --name-only
git blame <fix_commit>^ -- <file> | grep <relevant_lines>
阶段 2:按话题分组提交
将 fix 提交归组为话题(topic)。一个话题代表一类反复出现或相互关联的问题,同一话题在 ./postmortem/ 目录中对应一个文件。
2.1 分组策略(按优先级依次应用)
- 匹配已有话题文件:扫描
./postmortem/ 目录中已有的 \d{3}-<topic>.md 文件。如果新 fix 提交的 scope、修改文件、或 issue 引用与已有话题文件高度吻合 → 归入该已有话题候选(是否追加事故组块由阶段 3 决定)
- 按 issue 引用:引用相同
#issue 编号的提交 → 同一话题
- 按 scope + 语义相关性:具有相同
fix(scope) 且修复的是同一类问题(如都是"GDB 注入"相关) → 同一话题
- 按文件重叠 + 问题相似性:修改了相同核心文件且 commit message 描述的是同类故障 → 可能同一话题
- 新话题:不属于以上任何分组的提交 → 创建新话题
2.2 话题命名规范
话题文件命名格式:
\d{3}-<topic-slug>.md
\d{3}:三位数顺序编号(001、002、...),基于 ./postmortem/ 目录中已有文件的最大编号递增
<topic-slug>:kebab-case 的话题简短描述,概括该类问题的本质
示例:
001-gdb-attach-timeout.md — GDB 挂载超时相关的所有事故
002-tui-tab-order.md — TUI 标签页顺序相关的所有事故
003-python38-compatibility.md — Python 3.8 兼容性相关的所有事故
2.3 话题内的事故组块
每个话题文件内部可包含多个事故组块。一个事故组块代表该话题下的一次具体事故实例(通常对应一次或一组时间相近的 fix 提交)。
组块按时间倒序排列(最新的在最前面),便于快速查看最近的事故。
同一话题下何时拆分为不同组块:
- 不同时间段(跨 release)的修复 → 不同组块
- 同一 release 内但根因不同的修复 → 不同组块
- 同一根因、时间相近(48 小时内)的多个 fix 提交 → 合并为同一组块
阶段 3:候选评审与代表性筛选
本阶段决定“哪些 fix 值得写入 postmortem”。这是强制步骤,必须在生成或追加文件之前完成。
3.1 Review 目标 Postmortem 文件
对每个候选话题,在写入前必须先 review 目标文件:
- 已有话题:读取完整的
postmortem/<NNN>-<topic>.md,重点检查“案例索引”“话题概述”“最近 3 个事故组块”“行动项/预防”。
- 新话题:先 review
postmortem/README.md 和相邻/相似 topic 文件,确认没有已有话题可承载;如果只是现有话题的一个变体,应追加到已有文件而不是新建。
Review 后输出内部决策:
| 候选 | 目标文件 | 决策 | 原因 |
|---|
<commit> 或 <commit group> | <topic file> | include / merge / reference-only / skip | 是否带来新模式、是否重复、代表性如何 |
3.2 入选标准
满足以下任一条件的候选,通常应写成完整事故组块:
- 用户可见影响:导致 CLI/TUI/attach/diagnostic 命令失败、输出错误、挂起、崩溃或误导用户。
- 发布或 CI 阻断:导致 release workflow、容器测试、PyPI 发布或关键 CI gate 失败。
- 公开契约漂移:破坏 JSONL 字段、CLI 参数、命令返回 schema、兼容矩阵、配置格式等可被用户或自动化依赖的接口。
- 高严重级别:SEV-0/SEV-1 必须记录;SEV-2 通常记录;SEV-3 只在能提供新教训时记录;SEV-4 默认跳过。
- 新根因或新预防措施:即使影响较小,如果暴露了新的系统性缺口,也可以入选。
- 代表性强:一个 fix 能代表同一 release 中多次重复出现的问题,应优先选择它作为完整案例。
3.3 合并与去重规则
Postmortem 文件不需要包含该话题下的所有 fix。按以下规则减少噪音:
- 同根因合并:同一根因、同一用户症状、相近时间(默认 48 小时内)的多个 fix 合并为一个事故组块;所有相关修复提交列在“修复提交”表中。
- follow-up 合并:后续 fix 只是补测试、补文档、改命名、修 lint、调整断言或完善同一修复,应合并到主事故,不另开事故。
- 重复案例跳过:已有 topic 中已记录相同模式,且新 fix 没有新的触发条件、影响面或预防措施时,跳过完整组块;必要时只在“最近更新”或主事故参考中简短引用。
- 机械修复跳过:纯格式、拼写、导入顺序、测试 fixture 小修、无用户影响的内部清理,默认不写入 postmortem。
- 每话题限量:同一 release 对同一 topic 默认最多写 1-3 个完整事故组块;超过时必须说明为什么每个都具有独立代表性。
3.4 跳过也要可解释
跳过不等于忽略。对所有未入选的 fix,最终回复或内部工作记录中应能说明:
- 被合并到哪个事故组块;或
- 因为什么原因跳过(重复、低影响、测试-only、机械修复、无新教训);或
- 作为 reference-only 被哪个事故引用。
当用户明确要求“所有 fix 都要写入”时,才覆盖本筛选规则;否则默认以代表性和知识密度为优先。
阶段 4:分析入选事故组块
对每个入选事故组块进行深入分析,必须阅读实际代码变更。未入选的 fix 不需要完整事故分析,但合并/跳过理由必须可追溯。
4.1 判定严重级别
使用以下严重级别量表(改编自 Google SRE):
| 严重级别 | 标签 | 判定标准 | 示例 |
|---|
| SEV-0 | Critical | 数据丢失、安全漏洞、所有用户的功能完全中断 | 数据库损坏、认证绕过、启动即崩溃 |
| SEV-1 | High | 核心功能不可用,无替代方案 | 主要功能在常见条件下失败 |
| SEV-2 | Medium | 功能降级,存在替代方案 | 边缘情况下出错、性能回退 |
| SEV-3 | Low | 轻微问题、外观问题或罕见边缘情况 | UI 显示异常、错误消息拼写错误、极端负载下的竞态条件 |
| SEV-4 | Trivial | 无用户影响,仅内部可见 | 标记为 fix 的代码清理、测试修复、开发工具 |
严重级别推断信号(无显式标签时):
| 信号 | 倾向较高严重级别 | 倾向较低严重级别 |
|---|
| Fix 提交数量 | 同一问题 3+ 个提交 | 单个提交 |
| 修复时间跨度 | 修复持续数天 | 立即修复 |
| 修改的文件 | 核心模块(agent、injector、attach) | 测试、文档、配置 |
| 提交消息关键词 | "crash"、"broken"、"data loss"、"timeout"、"security" | "typo"、"cleanup"、"style"、"minor" |
| 破坏性变更标记 | 提交中包含 BREAKING CHANGE 或 ! | 无 |
4.2 判定根因
阅读每个入选 fix 提交或 commit group 的实际 diff:
git show <fix_commit_hash> --stat
git show <fix_commit_hash> -- <specific_file>
对根因进行分类:
| 类别 | 描述 | 示例 |
|---|
| Logic Error | 算法错误、条件判断错误、差一错误 | if x > 0 应为 if x >= 0 |
| Missing Validation | 输入未校验、边缘情况未处理 | API 响应未做 null 检查 |
| Race Condition | 时序依赖的故障、线程安全问题 | 共享状态未加锁 |
| Dependency Issue | 外部库 bug、版本不兼容 | 库的 API 在小版本中发生变化 |
| Configuration Error | 默认值错误、配置缺失、环境不匹配 | 硬编码路径、超时值不当 |
| Type Error | 类型假设错误、类型转换失败 | 预期 int 却收到 string |
| Resource Management | 泄漏、耗尽、清理不当 | 未关闭 socket、文件句柄泄漏 |
| Regression | 近期变更破坏了原本正常的代码 | 重构时移除了必要的检查 |
| Integration Error | 组件间不匹配 | API 契约违反 |
4.3 定位致因提交
利用阶段 1.4 收集的数据,定位引入 bug 的提交:
- 如果
git blame 指向引入问题行的特定提交 → 该提交即为致因提交
- 如果修复是对特定变更的回退 → 被回退的提交即为致因提交
- 如果提交 body 中注明 "introduced in abc1234" 或 "regression from PR #99" → 使用该信息
- 如果无法明确定位致因提交 → 声明"致因提交无法确定性定位",并列出候选提交
4.4 判定复现步骤
根据代码 diff 和提交消息,推断 bug 的复现方式:
- 触发 bug 的输入/条件是什么?
- 系统需要处于什么状态?
- 什么操作序列会导致故障?
- 可观察的症状是什么(错误消息、错误输出、崩溃)?
如果仅从代码无法明确复现方式,应声明:"复现方式需要进一步调查 — 详见代码 diff。"
4.5 记录修复内容
对事故组块中的每个 fix 提交:
- 改动了什么(diff 摘要)
- 为什么这样能修复根因
- 修复的折衷或局限性
4.6 提取经验教训
根据根因类别生成可操作的教训:
| 根因 | 典型教训 |
|---|
| Logic Error | 添加覆盖该精确边缘情况的单元测试;考虑属性基测试 |
| Missing Validation | 在模块边界添加输入校验;定义并强制执行不变量 |
| Race Condition | 审计共享可变状态;添加线程安全测试;文档化并发模型 |
| Dependency Issue | 锁定依赖版本;添加集成测试;监控变更日志 |
| Configuration Error | 在启动时添加配置校验;记录所有配置选项及其默认值 |
| Type Error | 添加类型注解;启用严格类型检查;使用运行时校验 |
| Regression | 提高被修改代码路径的测试覆盖率;添加回归测试 |
4.7 预防措施
提出具体、可操作的预防措施:
- 立即执行:现在就能做的(如添加特定测试)
- 短期:本迭代应完成的(如添加 CI 检查)
- 长期:纳入路线图考虑的(如架构变更)
阶段 5:生成或追加 Postmortem 报告
5.1 输出目录
mkdir -p ./postmortem
5.2 文件命名规范
./postmortem/<NNN>-<topic-slug>.md
<NNN>:三位数顺序编号(001、002、...)
<topic-slug>:kebab-case 话题简短描述
示例:
./postmortem/001-gdb-attach-timeout.md
./postmortem/002-tui-tab-order.md
./postmortem/003-dependency-version-constraints.md
5.3 确定编号
- 扫描
./postmortem/ 目录中已有文件
- 如果新事故属于已有话题 → 使用该文件,追加新的事故组块
- 如果是新话题 → 取已有文件的最大编号 + 1 作为新编号
ls ./postmortem/*.md 2>/dev/null | grep -oP '^\d{3}' | sort -n | tail -1
5.4 话题文件结构
每个话题文件必须遵循以下结构:
# <话题标题>
| 字段 | 值 |
|------|-----|
| **话题** | <话题简短描述> |
| **受影响组件** | <模块/scope> |
| **最高严重级别** | SEV-N (<Label>) |
| **事故次数** | N |
| **时间跨度** | YYYY-MM-DD 至 YYYY-MM-DD |
## 案例索引
| # | 事故 | 严重级别 | 日期 |
|---|------|----------|------|
| [#N](#事故-n事故简短描述) | <事故简短描述> | SEV-N | YYYY-MM-DD |
| ... | ... | ... | ... |
| [#1](#事故-1事故简短描述) | <最早的事故简短描述> | SEV-N | YYYY-MM-DD |
> 索引按时间倒序排列(与事故组块顺序一致),点击编号可跳转到对应事故。
## 话题概述
对该类问题的总体描述:反复出现的模式是什么、影响哪些模块、本质原因是什么。
每次新增事故组块后应更新此概述,反映最新的认知。
---
## 事故 #N:<事故简短描述>
> **Tag 范围**:`<start_tag>` → `<end_ref>` | **严重级别**:SEV-N | **日期**:YYYY-MM-DD
### 概要
一至三句话:发生了什么故障、影响范围、以及解决方式。
### 根因分析
#### 类别
<根因类别>
#### 分析
详细的技术说明,解释 bug 为何发生。
在有助于阐明原因时,引用 diff 中的代码片段。
#### 致因提交
引入该 bug 的提交:
| 提交 | 作者 | 日期 | 描述 |
|------|------|------|------|
| `<short_hash>` | <作者> | <日期> | <subject> |
> 如无法确定性定位致因提交,应明确说明并列出候选提交。
### 复现
#### 前置条件
- <所需系统状态>
#### 步骤
1. <步骤 1>
2. <步骤 2>
3. ...
#### 预期行为
<应该发生什么>
#### 实际行为
<实际发生了什么 — 错误消息、错误输出、崩溃等>
### 修复
#### 修复提交
| 提交 | 作者 | 日期 | 描述 |
|------|------|------|------|
| [`<short_hash>`](<link_if_available>) | <作者> | <日期> | <subject> |
#### 变更内容
修复的技术描述。包含相关代码片段。
#### 验证
修复的验证方式(新增测试、手动测试、CI 通过)。
### 影响
- **受影响用户**:<影响范围>
- **持续时间**:<bug 存在了多久 — 从致因提交到修复>
- **数据影响**:<是否存在数据损坏、丢失或不一致>
### 时间线
| 时间 | 事件 |
|------|------|
| <日期> | 提交 `<hash>` 引入了 bug |
| <日期> | 发现/报告了 bug |
| <日期> | 修复提交:`<hash>` |
| <日期> | 修复已验证 |
### 经验教训
#### 做得好的方面
- <关于发现或修复过程的正面观察>
#### 可以改进的方面
- <流程缺口、缺失的测试、发现延迟>
#### 行动项
| 行动 | 优先级 | 状态 |
|------|--------|------|
| <具体行动> | P0/P1/P2 | 待处理 |
### 预防
- **立即执行**:<现在就能做的>
- **短期**:<本迭代/冲刺应完成的>
- **长期**:<应考虑的架构或流程变更>
### 参考
- 修复 PR/提交:<链接>
- 相关 issue:<链接>
---
## 事故 #1:<最早的事故简短描述>
> **Tag 范围**:... | **严重级别**:... | **日期**:...
(同上格式)
关键规则:
- 事故组块按时间倒序排列(最新的
#N 在最前面,最早的 #1 在最后面)
- 文件顶部的元数据表(最高严重级别、事故次数、时间跨度)在每次追加后必须更新
- 案例索引在每次追加后必须同步更新(新增行、保持倒序)
- 话题概述在每次追加后应根据新认知进行更新
- 每个事故组块之间用
--- 水平线分隔
5.5 追加已有话题文件
当新事故归入已有话题时:
- 读取已有话题文件
- 确定当前最大事故编号
#N,新组块编号为 #(N+1)
- 将新事故组块插入到第一个
--- 分隔线之后(即话题概述之后、已有事故组块之前)
- 更新文件顶部的元数据表(事故次数 +1、时间跨度扩展、最高严重级别可能调整)
- 在案例索引表格顶部插入新事故行(保持倒序,最新在最前)
- 根据新事故的发现更新话题概述
阶段 6:更新汇总索引
在 ./postmortem/README.md 创建或更新汇总索引文件:
# Postmortem 索引
生成日期:YYYY-MM-DD
## 话题列表
| 编号 | 话题 | 最高严重级别 | 组件 | 事故次数 | 最近事故 |
|------|------|-------------|------|----------|----------|
| [001](./001-gdb-attach-timeout.md) | GDB 挂载超时 | SEV-2 | core | 3 | YYYY-MM-DD |
| [002](./002-tui-tab-order.md) | TUI 标签页顺序 | SEV-3 | tui | 1 | YYYY-MM-DD |
| [003](./003-dependency-version-constraints.md) | 依赖版本约束 | SEV-3 | dependencies | 2 | YYYY-MM-DD |
## 统计
- **话题总数**:N
- **事故总次数**:N
- **按严重级别**:SEV-0: N, SEV-1: N, SEV-2: N, SEV-3: N, SEV-4: N
- **按组件**:core: N, tui: N, cli: N, ...
- **按根因**:Logic Error: N, Missing Validation: N, ...
- **分析的 fix 提交总数**:N
- **入选事故组块数**:N
- **跳过/合并的 fix 提交数**:N
## 共性问题
<识别各话题间的重复模式。例如:>
- N 个话题与线程安全相关 → 建议进行并发审计
- N 个话题集中在同一模块 → 建议重构或增加测试覆盖率
- N 个话题源于缺失的输入校验 → 建议添加校验中间件
## 最近更新
| 日期 | 话题 | 事故 | 变更说明 |
|------|------|------|----------|
| YYYY-MM-DD | [001](./001-gdb-attach-timeout.md) | #3 | 新增事故:v0.1.6 中再次出现超时 |
| YYYY-MM-DD | [003](./003-dependency-version-constraints.md) | #2 | 新增事故:textual 版本约束 |
边缘情况
| 场景 | 行为 |
|---|
| 范围内无 fix 提交 | 报告:"在 <tag> 和 HEAD 之间未找到 fix 提交,无需分析。"不创建空的 postmortem。 |
| Tag 不存在 | 报错:"Tag <tag> 未找到。可用 tag 列表:..."并列出近期 tag。 |
| Fix 提交无 scope | 使用 "unscoped" 作为组件名。从修改的文件推断话题归属。 |
| Fix 提交引用了不存在的 issue | 记录该引用但不报错。标注为"引用的 issue #N(未找到)"。 |
| 话题仅含单个事故的单个 fix 提交 | 先按阶段 3 判断代表性;若有用户影响、新根因或新预防措施,则生成完整话题文件;否则跳过或并入相近话题。 |
| Fix 提交数量非常大(50+) | 按话题积极分组,并强制执行代表性筛选。优先记录 SEV-0 至 SEV-2;SEV-3 只记录新模式;SEV-4 默认跳过。 |
| 合并提交 | 跳过合并提交(使用 --no-merges 参数)。仅分析实际的 fix 提交。 |
| 约定式提交无 scope | 从修改的文件推断 scope(如 peeka/tui/ 中的变更 → scope 为 tui)。 |
| 新事故匹配多个已有话题 | 选择匹配度最高的话题。如果确实跨话题,在主话题中记录完整组块,在其他相关话题中添加交叉引用。 |
./postmortem/ 目录已有文件 | 不覆盖。识别已有话题进行追加,或创建新话题文件并使用下一个顺序编号。 |
语言
- 所有 postmortem 报告必须使用中文撰写
- 使用专业、技术性语言 — 不使用口语化表达
- 免责文化 — 聚焦于系统和流程,而非个人
- 事故描述使用被动语态("该函数被调用时..."而非"张三调用该函数时...")
- 必须具体 — 包含 commit hash、文件路径、行号和代码片段
重要注意事项
- 免责文化:Postmortem 聚焦于系统和流程,绝不指责个人。作者姓名仅用于提交表格中的归属标注,不在叙述中用于指责。
- 阅读实际代码:不要仅凭提交消息生成 postmortem。你必须阅读实际 diff(
git show)以理解变更内容,提供准确的根因分析。
- 代表性优先:postmortem 不是 fix commit 清单。除非用户明确要求全量记录,否则只写入最能代表风险模式、根因和预防措施的事故;重复 fix 应合并或跳过。
- 对不确定性保持诚实:如果仅从代码无法确定根因,应如实说明。"根因需要进一步调查"优于编造的解释。
- 话题文件的持续演进:话题文件是活文档。每次追加新事故后,应回顾并更新话题概述,反映对该类问题不断深入的理解。如果同一话题反复出现,应在话题概述中明确指出这是一个系统性问题。
- 仅限 Git 历史:本技能仅基于 git 历史工作。除非用户提供额外上下文,否则不访问 issue 追踪器、监控系统或事故管理工具。