72 lines
3.0 KiB
Bash
72 lines
3.0 KiB
Bash
#!/usr/bin/env bash
|
||
# check-changelog.sh — Claude Code Stop hook (bash 版)
|
||
#
|
||
# 检查源文件是否比 changelog-headlines.md 更新。如果更新了且本次会话确实有
|
||
# 文件编辑操作(通过 transcript_path 确认),提醒用户调用 dev-changelog Skill。
|
||
#
|
||
# 豁免条件(任一满足则静默退出):
|
||
# 1. changelog-headlines.md 不存在
|
||
# 2. .changelog-ack 文件足够新(本会话已通过 checklist 收尾项 Z 确认过)
|
||
# 3. stop_hook_active = true(Claude Code 正在从 Stop hook 里再次调用 Claude,避免递归)
|
||
# 4. transcript_path 中没有 Write/Edit 工具调用(无编辑证据,防跨会话 mtime 误触发)
|
||
# 5. 没有源文件比 changelog 更新
|
||
|
||
input=$(cat)
|
||
|
||
# === 豁免 1:stop_hook_active 防递归 ===
|
||
stop_hook_active=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('stop_hook_active',False))" 2>/dev/null || echo "False")
|
||
if [ "$stop_hook_active" = "True" ]; then
|
||
echo '{}'; exit 0
|
||
fi
|
||
|
||
changelog=".claude/changelog/changelog-headlines.md"
|
||
|
||
# === 豁免 2:changelog 文件不存在 ===
|
||
if [ ! -f "$changelog" ]; then
|
||
echo '{}'; exit 0
|
||
fi
|
||
|
||
clMtime=$(stat -c %Y "$changelog" 2>/dev/null || stat -f %m "$changelog" 2>/dev/null)
|
||
|
||
# === 豁免 3:ack 标记文件足够新 ===
|
||
ackFile=".claude/changelog/.changelog-ack"
|
||
if [ -f "$ackFile" ]; then
|
||
ackMtime=$(stat -c %Y "$ackFile" 2>/dev/null || stat -f %m "$ackFile" 2>/dev/null)
|
||
if [ "$ackMtime" -ge "$clMtime" ] 2>/dev/null; then
|
||
echo '{}'; exit 0
|
||
fi
|
||
fi
|
||
|
||
# === 豁免 4:transcript 中无编辑证据 ===
|
||
transcript_path=$(echo "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null || echo "")
|
||
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
|
||
if ! grep -qE '"name"\s*:\s*"(Write|Edit|MultiEdit|NotebookEdit)"' "$transcript_path"; then
|
||
echo '{}'; exit 0
|
||
fi
|
||
else
|
||
# transcript 不可用则从保守角度静默(宁可漏提醒,不可错误提醒)
|
||
echo '{}'; exit 0
|
||
fi
|
||
|
||
# === 源目录配置:若项目未自定义则默认扫描整个仓库根(排除常见生成物/依赖)===
|
||
srcDir="."
|
||
|
||
# === 核心检查:是否有源文件比 changelog 更新 ===
|
||
newerFile=$(find "$srcDir" -type f \( \
|
||
-name "*.py" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \
|
||
-o -name "*.css" -o -name "*.cs" -o -name "*.go" -o -name "*.rs" -o -name "*.java" \
|
||
\) \
|
||
! -path "*/node_modules/*" ! -path "*/.next/*" ! -path "*/__pycache__/*" \
|
||
! -path "*/venv/*" ! -path "*/.venv/*" ! -path "*/target/*" ! -path "*/bin/*" \
|
||
! -path "*/obj/*" ! -path "*/.claude/*" ! -path "*/.cursor/*" ! -path "*/.git/*" \
|
||
-newer "$changelog" -print -quit 2>/dev/null)
|
||
|
||
if [ -n "$newerFile" ]; then
|
||
fname=$(basename "$newerFile")
|
||
msg="[Hook] Source file updated (e.g. $fname) but changelog not synced. Run dev-changelog Skill operation A NOW to write all three changelog layers."
|
||
echo "{\"decision\":\"block\",\"reason\":\"$msg\"}"
|
||
else
|
||
echo '{}'
|
||
fi
|
||
exit 0
|