Files
vm-cloudflare/scripts/seed_ide_rules.py
Vault Sovereign 37a867c485 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
2025-12-16 18:31:53 +00:00

401 lines
12 KiB
Python

#!/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())