""" 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 工作流: 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 ") 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))