Skip to content

Commit 2e3248a

Browse files
calderbuildclaude
andcommitted
feat: add postmortem automation system for AI coding regression prevention
- Add tools/postmortem_init.py: onboarding script to analyze historical fix commits - Add tools/postmortem_check.py: multi-dimensional pattern matching for regression detection - Add tools/postmortem_generate.py: single-commit postmortem generation for CI - Add GitHub Actions workflows for PR checks and auto-generation - Generate 30 postmortems from historical fix commits using DeepSeek LLM This system creates project-specific regression knowledge base to prevent AI-assisted coding from reintroducing previously fixed bugs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3e221be commit 2e3248a

36 files changed

+2443
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Postmortem Check
2+
3+
on:
4+
pull_request:
5+
branches: [main, feature]
6+
paths:
7+
- 'app/**'
8+
- 'api/**'
9+
- 'tools/**'
10+
- 'public/**'
11+
push:
12+
branches: [main]
13+
paths:
14+
- 'app/**'
15+
- 'api/**'
16+
- 'tools/**'
17+
- 'public/**'
18+
19+
jobs:
20+
postmortem-check:
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
with:
27+
fetch-depth: 0
28+
29+
- name: Set up Python
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: '3.11'
33+
34+
- name: Install dependencies
35+
run: |
36+
pip install pyyaml
37+
38+
- name: Check for postmortem matches
39+
id: check
40+
run: |
41+
# Determine base ref
42+
if [ "${{ github.event_name }}" = "pull_request" ]; then
43+
BASE_REF="${{ github.event.pull_request.base.sha }}"
44+
else
45+
BASE_REF="HEAD~1"
46+
fi
47+
48+
# Check if postmortem directory exists
49+
if [ ! -d "postmortem" ]; then
50+
echo "No postmortem directory found. Skipping check."
51+
exit 0
52+
fi
53+
54+
# Run check (warn mode, don't block)
55+
python tools/postmortem_check.py \
56+
--base "$BASE_REF" \
57+
--mode warn \
58+
--threshold 0.7 \
59+
--output text 2>&1 | tee postmortem_output.txt
60+
61+
# Save output for PR comment
62+
echo "has_matches=false" >> $GITHUB_OUTPUT
63+
if grep -q "\[BLOCK\]\|\[WARN\]" postmortem_output.txt; then
64+
echo "has_matches=true" >> $GITHUB_OUTPUT
65+
fi
66+
continue-on-error: true
67+
68+
- name: Comment on PR
69+
if: github.event_name == 'pull_request' && steps.check.outputs.has_matches == 'true'
70+
uses: actions/github-script@v7
71+
with:
72+
script: |
73+
const fs = require('fs');
74+
const output = fs.readFileSync('postmortem_output.txt', 'utf8');
75+
76+
const body = `## Postmortem Alert
77+
78+
This PR may touch code related to past incidents. Please review:
79+
80+
\`\`\`
81+
${output.substring(0, 3000)}
82+
\`\`\`
83+
84+
Run locally with:
85+
\`\`\`bash
86+
python tools/postmortem_check.py --base ${{ github.event.pull_request.base.sha }}
87+
\`\`\`
88+
`;
89+
90+
// Check if we already commented
91+
const { data: comments } = await github.rest.issues.listComments({
92+
owner: context.repo.owner,
93+
repo: context.repo.repo,
94+
issue_number: context.issue.number,
95+
});
96+
97+
const existingComment = comments.find(c =>
98+
c.user.login === 'github-actions[bot]' &&
99+
c.body.includes('Postmortem Alert')
100+
);
101+
102+
if (existingComment) {
103+
await github.rest.issues.updateComment({
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
comment_id: existingComment.id,
107+
body: body
108+
});
109+
} else {
110+
await github.rest.issues.createComment({
111+
owner: context.repo.owner,
112+
repo: context.repo.repo,
113+
issue_number: context.issue.number,
114+
body: body
115+
});
116+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Postmortem Update
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
inputs:
8+
commit:
9+
description: 'Specific commit hash to process'
10+
required: false
11+
force:
12+
description: 'Force generation even if not a fix commit'
13+
type: boolean
14+
default: false
15+
16+
jobs:
17+
update-postmortems:
18+
runs-on: ubuntu-latest
19+
# Only run for fix commits or manual trigger
20+
if: |
21+
github.event_name == 'workflow_dispatch' ||
22+
startsWith(github.event.head_commit.message, 'fix')
23+
24+
steps:
25+
- name: Checkout code
26+
uses: actions/checkout@v4
27+
with:
28+
fetch-depth: 10
29+
token: ${{ secrets.GITHUB_TOKEN }}
30+
31+
- name: Set up Python
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: '3.11'
35+
36+
- name: Install dependencies
37+
run: |
38+
pip install pyyaml
39+
# Install project dependencies for LLM (optional)
40+
if [ -f requirements.txt ]; then
41+
pip install openai tiktoken tenacity || true
42+
fi
43+
44+
- name: Determine commit to process
45+
id: commit
46+
run: |
47+
if [ -n "${{ github.event.inputs.commit }}" ]; then
48+
echo "sha=${{ github.event.inputs.commit }}" >> $GITHUB_OUTPUT
49+
else
50+
echo "sha=${{ github.sha }}" >> $GITHUB_OUTPUT
51+
fi
52+
53+
- name: Generate postmortem
54+
env:
55+
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
56+
LLM_API_BASE: ${{ secrets.LLM_API_BASE }}
57+
LLM_MODEL: ${{ secrets.LLM_MODEL }}
58+
run: |
59+
COMMIT="${{ steps.commit.outputs.sha }}"
60+
FORCE_FLAG=""
61+
62+
if [ "${{ github.event.inputs.force }}" = "true" ]; then
63+
FORCE_FLAG="--force"
64+
fi
65+
66+
# Check if LLM is configured
67+
if [ -z "$LLM_API_KEY" ]; then
68+
echo "LLM_API_KEY not configured, using rule-based extraction"
69+
NO_LLM="--no-llm"
70+
else
71+
NO_LLM=""
72+
fi
73+
74+
# Run generation
75+
python tools/postmortem_generate.py \
76+
--commit "$COMMIT" \
77+
--output postmortem/ \
78+
$FORCE_FLAG \
79+
$NO_LLM || echo "Generation skipped or failed"
80+
81+
- name: Check for changes
82+
id: changes
83+
run: |
84+
if [ -n "$(git status --porcelain postmortem/)" ]; then
85+
echo "has_changes=true" >> $GITHUB_OUTPUT
86+
else
87+
echo "has_changes=false" >> $GITHUB_OUTPUT
88+
fi
89+
90+
- name: Commit postmortem updates
91+
if: steps.changes.outputs.has_changes == 'true'
92+
run: |
93+
git config user.name "github-actions[bot]"
94+
git config user.email "github-actions[bot]@users.noreply.github.com"
95+
96+
git add postmortem/
97+
git commit -m "docs(postmortem): auto-generated from ${{ steps.commit.outputs.sha }}"
98+
git push

postmortem/.gitkeep

Whitespace-only changes.

postmortem/PM-2025-001.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
id: PM-2025-001
2+
created_at: '2026-01-13T05:56:50.491275Z'
3+
source_commit: bb5c8b0
4+
severity: high
5+
title: 限制缓存大小以防止免费层内存溢出
6+
description: 在免费层环境中,因缓存未限制大小导致内存溢出,触发 Render 512MB 内存限制错误,影响服务稳定性。
7+
root_cause: Geocode 和 POI 缓存未设置大小限制,导致缓存无限增长,最终耗尽内存。
8+
triggers:
9+
files:
10+
- app/tool/*.py
11+
functions:
12+
- CafeRecommender.geocode_cache
13+
- CafeRecommender.poi_cache
14+
patterns:
15+
- geocode_cache
16+
- poi_cache
17+
- Field\(default_factory=dict\)
18+
keywords:
19+
- 缓存
20+
- cache
21+
- memory
22+
- OOM
23+
- 内存溢出
24+
fix_pattern:
25+
approach: 为缓存添加大小限制,并在超限时采用 FIFO 策略逐出最旧条目。
26+
key_changes:
27+
- 添加 GEOCODE_CACHE_MAX 和 POI_CACHE_MAX 常量,分别限制缓存大小为 100 和 50。
28+
- 在 geocode_cache 和 poi_cache 写入逻辑中添加 FIFO 驱逐逻辑。
29+
verification:
30+
- 验证 geocode_cache 和 poi_cache 的大小是否符合限制。
31+
- 检查 FIFO 驱逐逻辑是否正确执行。
32+
- 在内存受限环境中运行服务,确保不会触发 OOM 错误。
33+
- 测试缓存命中率是否符合预期,避免过多 API 调用。
34+
related:
35+
files_changed:
36+
- app/tool/meetspot_recommender.py
37+
tags:
38+
- geocoding
39+
- memory
40+
- cache
41+
- performance
42+
- api

postmortem/PM-2025-002.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
id: PM-2025-002
2+
created_at: '2026-01-13T05:56:55.380581Z'
3+
source_commit: 4d12fbe
4+
severity: medium
5+
title: 推荐系统搜索半径扩大至50公里以解决无结果问题
6+
description: 在推荐系统中,当使用fallback策略搜索地点无结果时,搜索半径被扩大至50公里(API最大值),以确保获取更多结果。此变更影响了推荐系统的搜索范围。
7+
root_cause: 原有的搜索半径限制导致在某些情况下无法获取足够的推荐结果。
8+
triggers:
9+
files:
10+
- app/tool/*.py
11+
functions:
12+
- _search_pois
13+
patterns:
14+
- radius=10000
15+
- radius=50000
16+
keywords:
17+
- fallback
18+
- 搜索半径
19+
- API最大
20+
fix_pattern:
21+
approach: 将搜索半径从10公里扩大到50公里以获取更多结果。
22+
key_changes:
23+
- radius=10000
24+
- radius=50000
25+
verification:
26+
- 验证在扩大搜索半径后,推荐系统能够返回更多的结果。
27+
- 确保API调用在50公里半径下正常工作。
28+
- 检查日志信息是否正确反映搜索半径的变化。
29+
related:
30+
files_changed:
31+
- app/tool/meetspot_recommender.py
32+
tags:
33+
- recommender
34+
- api
35+
- search radius

postmortem/PM-2025-003.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
id: PM-2025-003
2+
created_at: '2026-01-13T05:56:59.697193Z'
3+
source_commit: 3ba4b1d
4+
severity: high
5+
title: 修复地理编码跨城市歧义问题,提升演示可靠性
6+
description: 在地理编码过程中,某些地标名称可能导致跨城市的歧义,影响系统的准确性和演示的可靠性。此次修复通过添加地标映射解决了这一问题。
7+
root_cause: 缺乏对常见地标的明确城市映射,导致地理编码结果不准确。
8+
triggers:
9+
files:
10+
- app/tool/*.py
11+
functions:
12+
- _get_address
13+
patterns:
14+
- landmark_mapping\s*=\s*\{
15+
keywords:
16+
- geocoding
17+
- landmark
18+
- ambiguity
19+
- 城市歧义
20+
fix_pattern:
21+
approach: 通过添加45个以上的地标映射,确保地标名称能够正确解析到对应的城市。
22+
key_changes:
23+
- 新增 landmark_mapping 字典
24+
- 在 _get_address 函数中增加地标映射逻辑
25+
verification:
26+
- 验证地标名称是否正确映射到对应城市
27+
- 检查新增地标映射是否覆盖主要城市的常见地标
28+
- 确保地理编码结果在演示中表现良好
29+
related:
30+
files_changed:
31+
- app/tool/meetspot_recommender.py
32+
tags:
33+
- geocoding
34+
- data-mapping
35+
- 城市歧义
36+
- demo-reliability

postmortem/PM-2025-004.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
id: PM-2025-004
2+
created_at: '2026-01-13T05:57:03.601645Z'
3+
source_commit: a97fad3
4+
severity: medium
5+
title: 修复非合成动画以通过 Lighthouse 审核
6+
description: 移除了使用 width 属性的非 GPU 加速动画,解决了 Lighthouse 中的非合成动画审核失败问题。此问题可能导致页面性能下降,影响用户体验。
7+
root_cause: 部分动画使用了 width 和 box-shadow 等非 GPU 加速属性,导致 Lighthouse 审核失败。
8+
triggers:
9+
files:
10+
- docs/*.md
11+
- templates/pages/*.html
12+
- image*.png
13+
- '*.png'
14+
functions: []
15+
patterns:
16+
- .*width.*
17+
- .*box-shadow.*
18+
keywords:
19+
- non-composited animations
20+
- Lighthouse
21+
- GPU acceleration
22+
- transform
23+
- opacity
24+
fix_pattern:
25+
approach: 移除非 GPU 加速的动画属性,确保所有动画仅使用 transform 和 opacity。
26+
key_changes:
27+
- 移除了 slideProgress 和 typewriter 动画的 keyframes
28+
- '将 pulseGlow 动画的 box-shadow 替换为 transform: scale()'
29+
verification:
30+
- 检查所有动画是否仅使用 transform 和 opacity 属性
31+
- 运行 Lighthouse 审核,确保通过非合成动画检查
32+
- 验证页面性能是否提升
33+
related:
34+
files_changed:
35+
- '"docs/\347\216\260\345\234\272\345\261\225\346\274\224\350\257\235\346\234\257.md"'
36+
- image copy 2.png
37+
- image copy.png
38+
- image.png
39+
- templates/pages/home.html
40+
- '"\347\272\277\344\270\213\350\201\232\344\274\232\346\225\260\346\215\256.png"'
41+
tags:
42+
- ui
43+
- performance
44+
- animation
45+
- Lighthouse

0 commit comments

Comments
 (0)