- dashboard.html: 应用fI()闪烁修复到077c649版本(create-once/update-state pattern) - dashboard.py: 新增 /api/tests 端点 (import tests_api) - dashboard.py: /api/git 改用 git log 命令优先, 降级到 reflog - dashboard.py: /api/monitor + /api/expected 增加 Linux 支持(systemd timer/crontab) - dashboard.py: /api/spec + /api/prd 增加 246 venv 路径候选 - dashboard.py: /api/metagrowth git路径探测改善 - 恢复: meta_growth.py, tests_api.py, checklist_audit.py, service_registry.py - 恢复: sync-venv.sh, post-deploy-check.sh, meta-growth-design.md
389 lines
13 KiB
Python
389 lines
13 KiB
Python
"""
|
||
meta_growth.py — 元成长回路
|
||
|
||
分析修复型 commit 的模式,自动生成检测规则,注入监控管线。
|
||
Phase 3 核心组件,与 Tier3 漂移检测配合使用。
|
||
|
||
用法:
|
||
# 分析最近修复并生成规则(手动)
|
||
python meta_growth.py --analyze
|
||
|
||
# 查看当前规则清单
|
||
python meta_growth.py --list
|
||
|
||
# 激活规则(Stage 1 → 2)
|
||
python meta_growth.py --activate <rule_id>
|
||
|
||
工作流:
|
||
git log (fix commits)
|
||
→ diff_analyzer.py (提取变更模式)
|
||
→ classifier.py (分类修复类型)
|
||
→ rule_generator.py (生成检测规则)
|
||
→ grow_rules/growth_meta.json (持久化)
|
||
→ health_check 管线消费
|
||
"""
|
||
import os, sys, re, json, subprocess, hashlib
|
||
from datetime import datetime, timezone
|
||
|
||
# ─── 路径 ───────────────────────────────────────────────
|
||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
GROW_RULES_DIR = os.path.join(SCRIPT_DIR, "grow_rules")
|
||
TEMPLATES_PATH = os.path.join(SCRIPT_DIR, "pattern_templates", "templates.json")
|
||
GROWTH_META_PATH = os.path.join(GROW_RULES_DIR, "growth_meta.json")
|
||
|
||
# 项目根目录(用于 git 操作)
|
||
_PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", ".."))
|
||
_AMS_ROOT = os.environ.get("AGENTSMEETING_ROOT", "")
|
||
if _AMS_ROOT:
|
||
_PROJECT_DIR = _AMS_ROOT
|
||
|
||
os.makedirs(GROW_RULES_DIR, exist_ok=True)
|
||
|
||
# ─── 修复类型分类规则 ─────────────────────────────────
|
||
FIX_PATTERNS = [
|
||
{
|
||
"category": "port_misconfig",
|
||
"name": "端口/health 端点变更",
|
||
"patterns": [
|
||
r'port\s*[:=]\s*\d{4,5}',
|
||
r'health.*endpoint',
|
||
r':\d{4,5}/health',
|
||
r'health_check.*url',
|
||
],
|
||
"check_type": "port_health",
|
||
"confidence_base": 0.7,
|
||
},
|
||
{
|
||
"category": "path_error",
|
||
"name": "文件路径变更",
|
||
"patterns": [
|
||
r'os\.path\.(join|exists|normpath)',
|
||
r'Path\(.*\)',
|
||
r'sys\.path\.insert',
|
||
r'__file__',
|
||
r'AGENTSMEETING_ROOT',
|
||
],
|
||
"check_type": "file_exists",
|
||
"confidence_base": 0.6,
|
||
},
|
||
{
|
||
"category": "env_var",
|
||
"name": "环境变量变更",
|
||
"patterns": [
|
||
r'os\.environ\.get',
|
||
r'environ\[',
|
||
r'environ\.get\(',
|
||
],
|
||
"check_type": "env_check",
|
||
"confidence_base": 0.65,
|
||
},
|
||
{
|
||
"category": "cmd_arg",
|
||
"name": "命令/参数变更",
|
||
"patterns": [
|
||
r'subprocess\.run\(\[',
|
||
r'subprocess\.Popen\(\[',
|
||
r'pkill\b',
|
||
r'fuser\b',
|
||
r'pgrep\b',
|
||
],
|
||
"check_type": "cmd_available",
|
||
"confidence_base": 0.6,
|
||
},
|
||
{
|
||
"category": "timeout",
|
||
"name": "超时/重试变更",
|
||
"patterns": [
|
||
r'timeout\s*[:=]\s*\d+',
|
||
r'settimeout',
|
||
r'retry\b',
|
||
r'CREATION_NO_WINDOW',
|
||
r'threaded\s*[:=]\s*True',
|
||
],
|
||
"check_type": "timeout_threshold",
|
||
"confidence_base": 0.7,
|
||
},
|
||
{
|
||
"category": "import_fix",
|
||
"name": "导入/依赖变更",
|
||
"patterns": [
|
||
r'^import\s+\w+',
|
||
r'^from\s+\w+\s+import',
|
||
r'ModuleNotFoundError',
|
||
r'ImportError',
|
||
],
|
||
"check_type": "dependency",
|
||
"confidence_base": 0.5,
|
||
},
|
||
{
|
||
"category": "permission",
|
||
"name": "权限/安全变更",
|
||
"patterns": [
|
||
r'chmod\b',
|
||
r'ssh\b.*key',
|
||
r'token\b',
|
||
r'repr\(',
|
||
r'escap\w+\(',
|
||
],
|
||
"check_type": "security_scan",
|
||
"confidence_base": 0.4,
|
||
},
|
||
]
|
||
|
||
|
||
def git_log(fmt="%h %ad %s", n=20, date_fmt="%m-%d %H:%M", diff_filter="", pathspec=""):
|
||
"""执行 git log 返回行列表"""
|
||
cmd = ["git", "log", f"--format={fmt}", f"--date=format:{date_fmt}", f"-{n}"]
|
||
if diff_filter:
|
||
cmd.append(f"--diff-filter={diff_filter}")
|
||
if pathspec:
|
||
cmd.append("--")
|
||
cmd.append(pathspec)
|
||
try:
|
||
r = subprocess.run(cmd, cwd=_PROJECT_DIR, capture_output=True,
|
||
text=True, timeout=10, encoding="utf-8", errors="replace")
|
||
if r.returncode != 0:
|
||
return []
|
||
return [l.strip() for l in r.stdout.split("\n") if l.strip()]
|
||
except:
|
||
return []
|
||
|
||
|
||
def git_show(commit_hash):
|
||
"""返回 commit 的 diff stat + 完整 diff"""
|
||
try:
|
||
r = subprocess.run(
|
||
["git", "show", commit_hash, "--stat", "--"],
|
||
cwd=_PROJECT_DIR, capture_output=True, text=True,
|
||
timeout=10, encoding="utf-8", errors="replace")
|
||
stat = r.stdout or ""
|
||
r2 = subprocess.run(
|
||
["git", "diff", f"{commit_hash}^..{commit_hash}", "--"],
|
||
cwd=_PROJECT_DIR, capture_output=True, text=True,
|
||
timeout=10, encoding="utf-8", errors="replace")
|
||
full_diff = r2.stdout or ""
|
||
return stat, full_diff
|
||
except:
|
||
return "", ""
|
||
|
||
|
||
def is_fix_commit(msg):
|
||
"""判断 commit 是否为修复型提交"""
|
||
msg_lower = msg.lower()
|
||
fix_keywords = ["fix:", "bugfix", "hotfix", "correct", "修复", "修",
|
||
"resolve", "patch", "rollback", "revert"]
|
||
return any(kw in msg_lower for kw in fix_keywords)
|
||
|
||
|
||
def classify_fix(diff_text):
|
||
"""分析 diff 文本,返回匹配的修复分类列表"""
|
||
matches = []
|
||
for pattern_def in FIX_PATTERNS:
|
||
score = 0
|
||
for p in pattern_def["patterns"]:
|
||
try:
|
||
if re.search(p, diff_text, re.IGNORECASE):
|
||
score += 1
|
||
except re.error:
|
||
continue
|
||
if score > 0:
|
||
confidence = min(1.0, pattern_def["confidence_base"] + score * 0.1)
|
||
matches.append({
|
||
"category": pattern_def["category"],
|
||
"check_type": pattern_def["check_type"],
|
||
"name": pattern_def["name"],
|
||
"confidence": round(confidence, 2),
|
||
"match_count": score,
|
||
})
|
||
return matches
|
||
|
||
|
||
def generate_rule(commit_hash, commit_msg, classifications, stat_text):
|
||
"""根据分类结果生成检测规则"""
|
||
rules = []
|
||
for c in classifications:
|
||
rule_id = "meta-growth-" + hashlib.md5(
|
||
f"{commit_hash}:{c['category']}".encode()).hexdigest()[:6]
|
||
|
||
# 从 diff stat 中提取变更的文件类型做 target 信息
|
||
target_files = []
|
||
if stat_text:
|
||
for line in stat_text.split("\n"):
|
||
line = line.strip()
|
||
if line.endswith(".py") or "/" in line:
|
||
target_files.append(line.split()[-1] if line.split() else line)
|
||
|
||
rule = {
|
||
"rule_id": rule_id,
|
||
"source_commit": commit_hash,
|
||
"source_msg": commit_msg[:120],
|
||
"category": c["category"],
|
||
"check_type": c["check_type"],
|
||
"name": c["name"],
|
||
"confidence": c["confidence"],
|
||
"stage": 1,
|
||
"active": False,
|
||
"created": datetime.now(timezone.utc).isoformat(),
|
||
"target_files": target_files[:5],
|
||
"provenance": f"{commit_hash[:7]} {commit_msg[:80]}",
|
||
}
|
||
rules.append(rule)
|
||
return rules
|
||
|
||
|
||
def load_growth_meta():
|
||
"""加载现有规则清单"""
|
||
if os.path.exists(GROWTH_META_PATH):
|
||
try:
|
||
with open(GROWTH_META_PATH, "r", encoding="utf-8") as f:
|
||
return json.load(f)
|
||
except (json.JSONDecodeError, IOError):
|
||
pass
|
||
return {"rules": [], "stage": {"current": 1, "consecutive_fixes": 0}}
|
||
|
||
|
||
def save_growth_meta(data):
|
||
"""保存规则清单"""
|
||
with open(GROWTH_META_PATH, "w", encoding="utf-8") as f:
|
||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||
|
||
|
||
def merge_rules(existing, new_rules):
|
||
"""合并新规则到现有清单(去重)"""
|
||
existing_ids = {r["rule_id"] for r in existing["rules"]}
|
||
for rule in new_rules:
|
||
if rule["rule_id"] not in existing_ids:
|
||
existing["rules"].append(rule)
|
||
existing_ids.add(rule["rule_id"])
|
||
return existing
|
||
|
||
|
||
def stage_transition(meta):
|
||
"""根据连续修复次数推进 stage"""
|
||
consecutive = meta.get("stage", {}).get("consecutive_fixes", 0)
|
||
current_stage = meta.get("stage", {}).get("current", 1)
|
||
|
||
if current_stage == 1 and consecutive >= 3:
|
||
meta["stage"]["current"] = 2
|
||
# 激活 stage 1 规则
|
||
for r in meta["rules"]:
|
||
if r["stage"] == 1 and r["confidence"] >= 0.7:
|
||
r["active"] = True
|
||
print(f"[meta_growth] Stage 1 → 2 (连续 {consecutive} 次修复无退回)")
|
||
elif current_stage == 2 and consecutive >= 5:
|
||
meta["stage"]["current"] = 3
|
||
for r in meta["rules"]:
|
||
if r["stage"] <= 2 and r["confidence"] >= 0.6:
|
||
r["active"] = True
|
||
print(f"[meta_growth] Stage 2 → 3 (连续 {consecutive} 次泛化无退回)")
|
||
|
||
return meta
|
||
|
||
|
||
def analyze(commit_count=10):
|
||
"""主分析流程:读取最近 fix commits → 分类 → 生成规则"""
|
||
meta = load_growth_meta()
|
||
|
||
# 跳过已分析的 commits
|
||
analyzed_hashes = {r["source_commit"] for r in meta["rules"]}
|
||
|
||
# 获取最近的 fix commits
|
||
commits = git_log(n=commit_count)
|
||
new_rules = []
|
||
fix_count = 0
|
||
|
||
for line in commits:
|
||
parts = line.split(" ", 2)
|
||
if len(parts) < 3:
|
||
continue
|
||
h, dt, msg = parts[0], parts[1], parts[2]
|
||
if h in analyzed_hashes:
|
||
continue
|
||
if not is_fix_commit(msg):
|
||
continue
|
||
|
||
fix_count += 1
|
||
stat, diff = git_show(h)
|
||
classifications = classify_fix(diff)
|
||
if classifications:
|
||
rules = generate_rule(h, msg, classifications, stat)
|
||
new_rules.extend(rules)
|
||
print(f" {h} {dt} → {len(rules)} 条规则 ({', '.join(c['category'] for c in classifications)})")
|
||
|
||
if new_rules:
|
||
meta = merge_rules(meta, new_rules)
|
||
meta["stage"]["consecutive_fixes"] = meta["stage"].get("consecutive_fixes", 0) + fix_count
|
||
meta = stage_transition(meta)
|
||
save_growth_meta(meta)
|
||
print(f"\n[meta_growth] 新增 {len(new_rules)} 条规则,累计 {len(meta['rules'])} 条")
|
||
else:
|
||
print(f"[meta_growth] 未发现新的修复模式(最近 {commit_count} 条)")
|
||
|
||
return meta
|
||
|
||
|
||
def list_rules():
|
||
"""列出所有规则"""
|
||
meta = load_growth_meta()
|
||
print(f"\n元成长规则 | Stage {meta['stage']['current']} | "
|
||
f"连续修复: {meta['stage']['consecutive_fixes']} 次 | "
|
||
f"共 {len(meta['rules'])} 条规则\n")
|
||
print(f"{'ID':<22} {'类别':<16} {'置信度':<8} {'激活':<6} {'来源'}")
|
||
print("-" * 80)
|
||
for r in meta["rules"]:
|
||
active = "✅" if r.get("active") else "⏸️"
|
||
print(f"{r['rule_id']:<22} {r['category']:<16} "
|
||
f"{r['confidence']:<8} {active:<6} {r.get('provenance',''):<40}")
|
||
print()
|
||
|
||
|
||
def activate_rule(rule_id):
|
||
"""人工激活一条规则"""
|
||
meta = load_growth_meta()
|
||
for r in meta["rules"]:
|
||
if r["rule_id"] == rule_id:
|
||
r["active"] = True
|
||
save_growth_meta(meta)
|
||
print(f"[meta_growth] 规则 {rule_id} 已激活")
|
||
return
|
||
print(f"[meta_growth] 未找到规则: {rule_id}")
|
||
|
||
|
||
def api_status():
|
||
"""返回状态供 dashboard E Tab 调用"""
|
||
meta = load_growth_meta()
|
||
return {
|
||
"ok": True,
|
||
"status": "running" if meta["rules"] else "initial",
|
||
"current_stage": meta["stage"]["current"],
|
||
"consecutive_fixes": meta["stage"]["consecutive_fixes"],
|
||
"total_rules": len(meta["rules"]),
|
||
"active_rules": sum(1 for r in meta["rules"] if r.get("active")),
|
||
"categories": list({r["category"] for r in meta["rules"]}),
|
||
"latest_analysis": datetime.now(timezone.utc).isoformat(),
|
||
"description": "Phase 3 元成长回路 — 分析修复模式,自动扩展检测规则",
|
||
}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
if "--list" in sys.argv:
|
||
list_rules()
|
||
elif "--activate" in sys.argv:
|
||
idx = sys.argv.index("--activate")
|
||
if idx + 1 < len(sys.argv):
|
||
activate_rule(sys.argv[idx + 1])
|
||
else:
|
||
print("用法: python meta_growth.py --activate <rule_id>")
|
||
elif "--status" in sys.argv:
|
||
print(json.dumps(api_status(), indent=2, ensure_ascii=False))
|
||
else:
|
||
# 默认: 分析最近 20 条 commits
|
||
n = 20
|
||
if "--analyze" in sys.argv:
|
||
idx = sys.argv.index("--analyze")
|
||
if idx + 1 < len(sys.argv) and sys.argv[idx + 1].isdigit():
|
||
n = int(sys.argv[idx + 1])
|
||
meta = analyze(commit_count=n)
|
||
if "--json" in sys.argv:
|
||
print(json.dumps(meta, indent=2, ensure_ascii=False))
|