#!/usr/bin/env python3 """ migrate_rules_to_playbooks.py — 規則 → Playbook 遷移 CLI ========================================================= 將 alert_rules.yaml 中的 25 條規則遷移為 DRAFT Playbook,讓飛輪 RAG 有資料可查。 用法: # 預設 dry-run(只印計畫,不寫 DB) python scripts/migrate_rules_to_playbooks.py # 指定 yaml 路徑 python scripts/migrate_rules_to_playbooks.py --yaml-path /path/to/alert_rules.yaml # 真實寫入 DB python scripts/migrate_rules_to_playbooks.py --commit # 完整選項 python scripts/migrate_rules_to_playbooks.py --yaml-path alert_rules.yaml --commit W1 PR-R1 — 規則 → Playbook 遷移 2026-04-28 ogt + Claude Sonnet 4.6 """ from __future__ import annotations import argparse import asyncio import os import sys from pathlib import Path # 確保 apps/api/src 在 import path 中(從 scripts/ 執行時) _SCRIPT_DIR = Path(__file__).parent _API_ROOT = _SCRIPT_DIR.parent sys.path.insert(0, str(_API_ROOT)) # 預設 yaml 路徑:相對 scripts/ 的上一層(apps/api/alert_rules.yaml) _DEFAULT_YAML_PATH = _API_ROOT / "alert_rules.yaml" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="將 alert_rules.yaml 遷移為 DRAFT Playbook(飛輪 RAG 冷啟動)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 範例: python scripts/migrate_rules_to_playbooks.py # dry-run(預設) python scripts/migrate_rules_to_playbooks.py --commit # 真實寫入 python scripts/migrate_rules_to_playbooks.py --yaml-path alert_rules.yaml --commit """, ) parser.add_argument( "--yaml-path", type=Path, default=_DEFAULT_YAML_PATH, help=f"alert_rules.yaml 路徑(預設: {_DEFAULT_YAML_PATH})", ) parser.add_argument( "--commit", action="store_true", default=False, help="真實寫入 DB(預設 dry-run,僅印計畫)", ) parser.add_argument( "--disable-flag", action="store_true", default=False, help="模擬 ENABLE_RULE_MIGRATION_DRAFT=false(測試 feature flag 關閉路徑)", ) # 2026-04-29 ogt + Claude Opus 4.7: critic Major #2 修 # --commit 寫 prod DB 必須二次確認,誤跑會在 prod 製造 25 筆 DRAFT parser.add_argument( "--yes", action="store_true", default=False, help="跳過 --commit 的二次確認 prompt(CI / 自動化用)", ) return parser.parse_args() async def _run(args: argparse.Namespace) -> int: """ 非同步主流程 Returns: exit code (0=成功, 1=有錯誤) """ from src.services.rule_to_playbook_migrator import migrate_yaml_rules_to_playbooks yaml_path: Path = args.yaml_path dry_run: bool = not args.commit enable_migration: bool = not args.disable_flag # 讀取 feature flag(環境變數優先,CLI flag 次之) env_flag = os.environ.get("ENABLE_RULE_MIGRATION_DRAFT", "").lower() if env_flag == "false": enable_migration = False print(f"\n{'[DRY-RUN] ' if dry_run else ''}規則 → Playbook 遷移") print(f" yaml_path: {yaml_path}") print(f" enable_migration: {enable_migration}") print(f" dry_run: {dry_run}") print() if not yaml_path.exists(): print(f"[ERROR] yaml 不存在: {yaml_path}", file=sys.stderr) return 1 # 2026-04-29 critic Major #2 修:--commit 二次確認,--yes 跳過 if not dry_run and not args.yes: ans = input( "⚠️ 即將寫入 prod DB(最多 25 筆 DRAFT Playbook)\n" " Type 'yes' to confirm (or 'n' to abort): " ).strip().lower() if ans != "yes": print("[ABORTED] 使用者取消(type 'yes' to confirm)", file=sys.stderr) return 1 report = await migrate_yaml_rules_to_playbooks( yaml_path=yaml_path, dry_run=dry_run, enable_migration=enable_migration, ) # 輸出報告 print("=" * 60) print(report.summary()) print("=" * 60) if report.created_names: action = "待建立" if dry_run else "已建立" print(f"\n{action} ({len(report.created_names)} 條):") for name in report.created_names: print(f" + {name}") if report.skipped_names: print(f"\n已跳過(已存在)({len(report.skipped_names)} 條):") for name in report.skipped_names: print(f" ~ {name}") if report.errors: print(f"\n[ERROR] 失敗 ({len(report.errors)} 條):", file=sys.stderr) for err in report.errors: print(f" ! {err}", file=sys.stderr) if dry_run and report.created > 0: print(f"\n提示: 加 --commit 參數執行實際寫入(將建立 {report.created} 條 DRAFT Playbook)") return 1 if report.failed > 0 else 0 def main() -> None: args = parse_args() exit_code = asyncio.run(_run(args)) sys.exit(exit_code) if __name__ == "__main__": main()