在准备环境前提交次全部更改。
This commit is contained in:
107
.kiro/scripts/audit_reminder.py
Normal file
107
.kiro/scripts/audit_reminder.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""audit_reminder — Agent 结束时检查是否有待审计改动,15 分钟限频提醒。
|
||||
|
||||
替代原 PowerShell 版本,避免 Windows PowerShell 5.1 解析器 bug。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
TZ_TAIPEI = timezone(timedelta(hours=8))
|
||||
STATE_PATH = os.path.join(".kiro", ".audit_state.json")
|
||||
MIN_INTERVAL = timedelta(minutes=15)
|
||||
|
||||
|
||||
def now_taipei():
|
||||
return datetime.now(TZ_TAIPEI)
|
||||
|
||||
|
||||
def load_state():
|
||||
if not os.path.isfile(STATE_PATH):
|
||||
return None
|
||||
try:
|
||||
with open(STATE_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def save_state(state):
|
||||
os.makedirs(".kiro", exist_ok=True)
|
||||
with open(STATE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def get_real_changes():
|
||||
"""获取排除噪声后的变更文件"""
|
||||
try:
|
||||
r = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0:
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
files = []
|
||||
for line in r.stdout.splitlines():
|
||||
if len(line) < 4:
|
||||
continue
|
||||
path = line[3:].strip().strip('"').replace("\\", "/")
|
||||
if " -> " in path:
|
||||
path = path.split(" -> ")[-1]
|
||||
# 排除审计产物、.kiro 配置、临时文件
|
||||
if path and not path.startswith("docs/audit/") and not path.startswith(".kiro/") and not path.startswith("tmp/") and not path.startswith(".hypothesis/"):
|
||||
files.append(path)
|
||||
return sorted(set(files))
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
if not state:
|
||||
sys.exit(0)
|
||||
|
||||
if not state.get("audit_required"):
|
||||
sys.exit(0)
|
||||
|
||||
# 工作树干净时清除审计状态
|
||||
real_files = get_real_changes()
|
||||
if not real_files:
|
||||
state["audit_required"] = False
|
||||
state["reasons"] = []
|
||||
state["changed_files"] = []
|
||||
state["last_reminded_at"] = None
|
||||
save_state(state)
|
||||
sys.exit(0)
|
||||
|
||||
now = now_taipei()
|
||||
|
||||
# 15 分钟限频
|
||||
last_str = state.get("last_reminded_at")
|
||||
if last_str:
|
||||
try:
|
||||
last = datetime.fromisoformat(last_str)
|
||||
if (now - last) < MIN_INTERVAL:
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 更新提醒时间
|
||||
state["last_reminded_at"] = now.isoformat()
|
||||
save_state(state)
|
||||
|
||||
reasons = state.get("reasons", [])
|
||||
reason_text = ", ".join(reasons) if reasons else "high-risk paths changed"
|
||||
sys.stderr.write(
|
||||
f"[AUDIT REMINDER] Pending audit detected ({reason_text}). "
|
||||
f"Run /audit (Manual: Run /audit hook) to sync docs & write audit artifacts. "
|
||||
f"(rate limit: 15min)\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user