Files
AgentsMeeting/gateway/scripts/meta_growth.py
T
hmo b11b88887c merge: 恢复077c649内容(开发原则/K tests/spec/prd paths) + 保留flicker-free EasyTier/RDP + Linux兼容改善
- 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
2026-07-15 10:10:25 +08:00

389 lines
13 KiB
Python
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.
"""
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))