Files
CursorInitGeneral/.cursor/hooks/check-changelog.sh
2026-07-27 15:09:13 +08:00

166 lines
4.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Changelog sync guard按活动扫描配置提示本会话内尚未提交的项目改动。
emit_empty() {
printf '{}\n'
}
cat >/dev/null
if [ -n "${CURSOR_SKIP_CHANGELOG:-}" ]; then
emit_empty
exit 0
fi
script_dir=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)
cursor_dir=$(dirname -- "$script_dir")
repo_root=$(dirname -- "$cursor_dir")
changelog="$cursor_dir/changelog/changelog-headlines.md"
ack_file="$cursor_dir/changelog/.changelog-ack"
session_marker="$cursor_dir/changelog/.session-start"
scan_config="$script_dir/changelog-scan.json"
if [ ! -f "$changelog" ] || [ ! -f "$scan_config" ]; then
emit_empty
exit 0
fi
if [ ! -f "$session_marker" ]; then
emit_empty
exit 0
fi
get_mtime() {
stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null
}
if [ -f "$ack_file" ]; then
changelog_mtime=$(get_mtime "$changelog")
ack_mtime=$(get_mtime "$ack_file")
if [ -n "$changelog_mtime" ] && [ -n "$ack_mtime" ] &&
[ "$ack_mtime" -ge "$changelog_mtime" ] 2>/dev/null; then
emit_empty
exit 0
fi
fi
python_cmd=''
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1 &&
"$candidate" -c 'import json' >/dev/null 2>&1; then
python_cmd=$candidate
break
fi
done
if [ -z "$python_cmd" ]; then
emit_empty
exit 0
fi
result=$(
"$python_cmd" - "$repo_root" "$changelog" "$session_marker" "$scan_config" <<'PY'
import json
import os
from pathlib import Path
import subprocess
import sys
repo_root = Path(sys.argv[1]).resolve()
changelog = Path(sys.argv[2])
session_marker = Path(sys.argv[3])
scan_config = Path(sys.argv[4])
def emit(value):
print(json.dumps(value, ensure_ascii=False, separators=(",", ":")))
try:
config = json.loads(scan_config.read_text(encoding="utf-8"))
if config.get("schema_version") != 1:
raise ValueError("unsupported schema")
names = {
value.casefold()
for value in config.get("included_names", [])
if isinstance(value, str) and value
}
extensions = {
value.casefold()
for value in config.get("included_extensions", [])
if isinstance(value, str) and value
}
excluded = {
value.casefold()
for value in config.get("excluded_directories", [])
if isinstance(value, str) and value
}
if not names and not extensions:
emit({})
raise SystemExit(0)
status = subprocess.run(
["git", "-C", str(repo_root), "status", "--porcelain=v1",
"--untracked-files=all", "--"],
check=False,
capture_output=True,
text=True,
encoding="utf-8",
)
if status.returncode != 0 or not status.stdout.strip():
emit({})
raise SystemExit(0)
comparison_mtime = max(
changelog.stat().st_mtime,
session_marker.stat().st_mtime,
)
for current_root, directory_names, file_names in os.walk(repo_root):
directory_names[:] = [
name for name in directory_names if name.casefold() not in excluded
]
current = Path(current_root)
for file_name in file_names:
candidate = current / file_name
if (
file_name.casefold() not in names
and candidate.suffix.casefold() not in extensions
):
continue
try:
if candidate.stat().st_mtime <= comparison_mtime:
continue
except OSError:
continue
relative = candidate.relative_to(repo_root).as_posix()
path_status = subprocess.run(
["git", "-C", str(repo_root), "status", "--porcelain=v1",
"--untracked-files=all", "--", relative],
check=False,
capture_output=True,
text=True,
encoding="utf-8",
)
if path_status.returncode == 0 and path_status.stdout.strip():
message = (
"[Hook] Project source/config has an uncommitted change newer "
f"than this session and the changelog (e.g. {relative}). "
"Run dev-changelog Skill operation A now: add a fragment under "
".cursor/changelog/entries/ and rebuild the changelog views."
)
emit({"followup_message": message})
raise SystemExit(0)
emit({})
except SystemExit:
raise
except Exception:
emit({})
PY
) || result='{}'
if [ -n "$result" ]; then
printf '%s\n' "$result"
else
emit_empty
fi
exit 0