Initial commit: Cloudflare infrastructure with WAF Intelligence
- Complete Cloudflare Terraform configuration (DNS, WAF, tunnels, access) - WAF Intelligence MCP server with threat analysis and ML classification - GitOps automation with PR workflows and drift detection - Observatory monitoring stack with Prometheus/Grafana - IDE operator rules for governed development - Security playbooks and compliance frameworks - Autonomous remediation and state reconciliation
This commit is contained in:
400
scripts/seed_ide_rules.py
Normal file
400
scripts/seed_ide_rules.py
Normal file
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IDE Operator Rules Seeder
|
||||
|
||||
Seeds operator rules into VS Code extension folders to provide
|
||||
policy-aware guidance for AI assistants and code generation.
|
||||
|
||||
This script:
|
||||
1. Finds VS Code extension directories
|
||||
2. Copies/symlinks operator rules to the appropriate locations
|
||||
3. Works across Mac, Linux, and Windows
|
||||
4. Can watch for extension updates and auto-reseed
|
||||
5. Verifies symlink integrity
|
||||
|
||||
Usage:
|
||||
python seed_ide_rules.py # Auto-detect and seed
|
||||
python seed_ide_rules.py --list # List target directories
|
||||
python seed_ide_rules.py --symlink # Use symlinks instead of copy
|
||||
python seed_ide_rules.py --dry-run # Show what would be done
|
||||
python seed_ide_rules.py --watch # Watch for extension updates and auto-reseed
|
||||
python seed_ide_rules.py --verify # Verify all symlinks are intact
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Set, Tuple
|
||||
|
||||
|
||||
# Source rules files to seed
|
||||
RULES_FILES = [
|
||||
"IDE_OPERATOR_RULES.md",
|
||||
"AGENT_GUARDRAILS.md",
|
||||
]
|
||||
|
||||
# Target extension patterns and their rule directories
|
||||
EXTENSION_TARGETS = [
|
||||
# Azure GitHub Copilot extension
|
||||
{
|
||||
"pattern": "ms-azuretools.vscode-azure-github-copilot-*",
|
||||
"subdir": "resources/azureRules",
|
||||
"target_name": "cloudflare.instructions.md",
|
||||
},
|
||||
# GitHub Copilot extension (if it has a rules dir)
|
||||
{
|
||||
"pattern": "github.copilot-*",
|
||||
"subdir": "resources",
|
||||
"target_name": "operator.instructions.md",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_vscode_extensions_dirs() -> List[Path]:
|
||||
"""Get VS Code extension directories for the current platform."""
|
||||
system = platform.system()
|
||||
home = Path.home()
|
||||
|
||||
dirs: List[Path] = []
|
||||
|
||||
if system == "Darwin": # macOS
|
||||
dirs = [
|
||||
home / ".vscode" / "extensions",
|
||||
home / ".vscode-insiders" / "extensions",
|
||||
home / ".cursor" / "extensions", # Cursor editor
|
||||
]
|
||||
elif system == "Linux":
|
||||
dirs = [
|
||||
home / ".vscode" / "extensions",
|
||||
home / ".vscode-server" / "extensions", # Remote SSH
|
||||
home / ".vscode-insiders" / "extensions",
|
||||
]
|
||||
elif system == "Windows":
|
||||
dirs = [
|
||||
home / ".vscode" / "extensions",
|
||||
home / ".vscode-insiders" / "extensions",
|
||||
Path(os.environ.get("APPDATA", "")) / "Code" / "User" / "extensions",
|
||||
]
|
||||
|
||||
return [d for d in dirs if d.exists()]
|
||||
|
||||
|
||||
def find_target_extensions(base_dirs: List[Path]) -> List[Tuple[Path, dict]]:
|
||||
"""Find matching extension directories."""
|
||||
targets: List[Tuple[Path, dict]] = []
|
||||
|
||||
for base_dir in base_dirs:
|
||||
for ext_config in EXTENSION_TARGETS:
|
||||
pattern = ext_config["pattern"]
|
||||
# Use glob to find matching extensions
|
||||
for ext_path in base_dir.glob(pattern):
|
||||
if ext_path.is_dir():
|
||||
targets.append((ext_path, ext_config))
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
def get_source_rules_path() -> Path:
|
||||
"""Get the path to the source rules file."""
|
||||
# Try relative to this script first
|
||||
script_dir = Path(__file__).parent.parent
|
||||
|
||||
for rules_file in RULES_FILES:
|
||||
source = script_dir / rules_file
|
||||
if source.exists():
|
||||
return source
|
||||
|
||||
# Try current working directory
|
||||
for rules_file in RULES_FILES:
|
||||
source = Path.cwd() / rules_file
|
||||
if source.exists():
|
||||
return source
|
||||
|
||||
# Try parent of cwd (in case running from scripts/)
|
||||
for rules_file in RULES_FILES:
|
||||
source = Path.cwd().parent / rules_file
|
||||
if source.exists():
|
||||
return source
|
||||
|
||||
raise FileNotFoundError(
|
||||
f"Could not find any of {RULES_FILES}. "
|
||||
"Run this script from the CLOUDFLARE repo root."
|
||||
)
|
||||
|
||||
|
||||
def seed_rules(
|
||||
source: Path,
|
||||
targets: List[Tuple[Path, dict]],
|
||||
use_symlink: bool = False,
|
||||
dry_run: bool = False,
|
||||
) -> List[str]:
|
||||
"""Seed rules to target directories."""
|
||||
results: List[str] = []
|
||||
|
||||
for ext_path, config in targets:
|
||||
subdir = config["subdir"]
|
||||
target_name = config["target_name"]
|
||||
|
||||
target_dir = ext_path / subdir
|
||||
target_file = target_dir / target_name
|
||||
|
||||
# Create target directory if needed
|
||||
if not dry_run:
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
action = "symlink" if use_symlink else "copy"
|
||||
|
||||
if dry_run:
|
||||
results.append(f"[DRY RUN] Would {action}: {source} → {target_file}")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Remove existing file/symlink
|
||||
if target_file.exists() or target_file.is_symlink():
|
||||
target_file.unlink()
|
||||
|
||||
if use_symlink:
|
||||
target_file.symlink_to(source.resolve())
|
||||
results.append(f"✅ Symlinked: {target_file}")
|
||||
else:
|
||||
shutil.copy2(source, target_file)
|
||||
results.append(f"✅ Copied: {target_file}")
|
||||
|
||||
except PermissionError:
|
||||
results.append(f"❌ Permission denied: {target_file}")
|
||||
except Exception as e:
|
||||
results.append(f"❌ Failed: {target_file} — {e}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def list_targets(targets: List[Tuple[Path, dict]]) -> None:
|
||||
"""List all target directories."""
|
||||
print("\n📁 Found VS Code extension targets:\n")
|
||||
|
||||
if not targets:
|
||||
print(" No matching extensions found.")
|
||||
print(" Install ms-azuretools.vscode-azure-github-copilot to enable seeding.")
|
||||
return
|
||||
|
||||
for ext_path, config in targets:
|
||||
print(f" 📦 {ext_path.name}")
|
||||
print(f" Path: {ext_path}")
|
||||
print(f" Target: {config['subdir']}/{config['target_name']}")
|
||||
print()
|
||||
|
||||
|
||||
def verify_symlinks(
|
||||
targets: List[Tuple[Path, dict]],
|
||||
source: Path,
|
||||
) -> List[str]:
|
||||
"""Verify all symlinks point to correct source."""
|
||||
results: List[str] = []
|
||||
|
||||
for ext_path, config in targets:
|
||||
target_file = ext_path / config["subdir"] / config["target_name"]
|
||||
|
||||
if target_file.is_symlink():
|
||||
try:
|
||||
if target_file.resolve() == source.resolve():
|
||||
results.append(f"✅ Valid: {config['target_name']} in {ext_path.name}")
|
||||
else:
|
||||
results.append(
|
||||
f"⚠️ Stale: {target_file.name} → {target_file.resolve()}"
|
||||
)
|
||||
except OSError:
|
||||
results.append(f"💀 Broken symlink: {target_file}")
|
||||
elif target_file.exists():
|
||||
results.append(f"📄 Copy (not symlink): {target_file.name} in {ext_path.name}")
|
||||
else:
|
||||
results.append(f"❌ Missing: {config['target_name']} in {ext_path.name}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def watch_and_reseed(
|
||||
source: Path,
|
||||
use_symlink: bool = True,
|
||||
interval: int = 60,
|
||||
) -> None:
|
||||
"""Watch for new extensions and auto-reseed."""
|
||||
print(f"👁️ Watching for extension updates (every {interval}s)...")
|
||||
print(" Press Ctrl+C to stop\n")
|
||||
|
||||
known_extensions: Set[str] = set()
|
||||
|
||||
# Initial seed
|
||||
base_dirs = get_vscode_extensions_dirs()
|
||||
targets = find_target_extensions(base_dirs)
|
||||
known_extensions = {str(t[0]) for t in targets}
|
||||
|
||||
results = seed_rules(source, targets, use_symlink=use_symlink)
|
||||
seeded = sum(1 for r in results if r.startswith("✅"))
|
||||
print(f"📊 Initial seed: {seeded}/{len(results)} targets")
|
||||
|
||||
while True:
|
||||
try:
|
||||
time.sleep(interval)
|
||||
|
||||
base_dirs = get_vscode_extensions_dirs()
|
||||
targets = find_target_extensions(base_dirs)
|
||||
current = {str(t[0]) for t in targets}
|
||||
|
||||
new_extensions = current - known_extensions
|
||||
removed_extensions = known_extensions - current
|
||||
|
||||
if new_extensions:
|
||||
print(f"\n🆕 {len(new_extensions)} new extension(s) detected")
|
||||
# Only seed new ones
|
||||
new_targets = [(p, c) for p, c in targets if str(p) in new_extensions]
|
||||
results = seed_rules(source, new_targets, use_symlink=use_symlink)
|
||||
for r in results:
|
||||
print(f" {r}")
|
||||
|
||||
if removed_extensions:
|
||||
print(f"\n🗑️ {len(removed_extensions)} extension(s) removed")
|
||||
|
||||
known_extensions = current
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 Stopped watching")
|
||||
break
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Seed IDE operator rules into VS Code extensions"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list", "-l",
|
||||
action="store_true",
|
||||
help="List target extension directories",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--symlink", "-s",
|
||||
action="store_true",
|
||||
help="Use symlinks instead of copying files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", "-n",
|
||||
action="store_true",
|
||||
help="Show what would be done without making changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--watch", "-w",
|
||||
action="store_true",
|
||||
help="Watch for extension updates and auto-reseed (runs in foreground)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verify", "-v",
|
||||
action="store_true",
|
||||
help="Verify all symlinks are intact",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interval",
|
||||
type=int,
|
||||
default=60,
|
||||
help="Watch interval in seconds (default: 60)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
type=Path,
|
||||
help="Source rules file (default: auto-detect)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Find VS Code extension directories
|
||||
base_dirs = get_vscode_extensions_dirs()
|
||||
|
||||
if not base_dirs:
|
||||
print("❌ No VS Code extension directories found.")
|
||||
print(" Make sure VS Code is installed.")
|
||||
return 1
|
||||
|
||||
print(f"🔍 Searching in {len(base_dirs)} VS Code extension directories...")
|
||||
|
||||
# Find target extensions
|
||||
targets = find_target_extensions(base_dirs)
|
||||
|
||||
if args.list:
|
||||
list_targets(targets)
|
||||
return 0
|
||||
|
||||
if not targets:
|
||||
print("\n⚠️ No matching extensions found.")
|
||||
print(" Install one of these extensions to enable rule seeding:")
|
||||
print(" - ms-azuretools.vscode-azure-github-copilot")
|
||||
print(" - github.copilot")
|
||||
return 1
|
||||
|
||||
# Get source file
|
||||
try:
|
||||
source = args.source or get_source_rules_path()
|
||||
except FileNotFoundError as e:
|
||||
print(f"❌ {e}")
|
||||
return 1
|
||||
|
||||
# Handle --verify
|
||||
if args.verify:
|
||||
print(f"📄 Source: {source}")
|
||||
print(f"🔍 Verifying {len(targets)} target(s)...\n")
|
||||
results = verify_symlinks(targets, source)
|
||||
print("\n".join(results))
|
||||
|
||||
valid = sum(1 for r in results if r.startswith("✅"))
|
||||
stale = sum(1 for r in results if r.startswith("⚠️"))
|
||||
missing = sum(1 for r in results if r.startswith("❌"))
|
||||
broken = sum(1 for r in results if r.startswith("💀"))
|
||||
|
||||
print(f"\n📊 {valid}/{len(results)} symlinks valid")
|
||||
if stale:
|
||||
print(f" ⚠️ {stale} stale (run --symlink to fix)")
|
||||
if missing:
|
||||
print(f" ❌ {missing} missing (run --symlink to create)")
|
||||
if broken:
|
||||
print(f" 💀 {broken} broken (run --symlink to fix)")
|
||||
|
||||
return 0 if (missing == 0 and broken == 0) else 1
|
||||
|
||||
# Handle --watch
|
||||
if args.watch:
|
||||
print(f"📄 Source: {source}")
|
||||
watch_and_reseed(source, use_symlink=True, interval=args.interval)
|
||||
return 0
|
||||
|
||||
print(f"📄 Source: {source}")
|
||||
print(f"🎯 Found {len(targets)} target extension(s)")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n🔍 Dry run mode — no changes will be made\n")
|
||||
|
||||
# Seed the rules
|
||||
results = seed_rules(
|
||||
source=source,
|
||||
targets=targets,
|
||||
use_symlink=args.symlink,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
|
||||
print("\n" + "\n".join(results))
|
||||
|
||||
# Summary
|
||||
success = sum(1 for r in results if r.startswith("✅"))
|
||||
failed = sum(1 for r in results if r.startswith("❌"))
|
||||
|
||||
if not args.dry_run:
|
||||
print(f"\n📊 Seeded {success}/{len(results)} targets")
|
||||
if failed:
|
||||
print(f" ⚠️ {failed} failed — check permissions")
|
||||
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user