Skill: kofna3369/cron-evaluator (version 1.0.0)

--- name: cron-evaluator description: | Cron Evaluator — Analyzes and scores cron jobs on a system for health, resource usage, collision risk, and resilience. Provides actionable suggestions for improvement. Use when: (1) auditing cron jobs, (2) detecting timing collisions, (3) optimizing cron performance, (4) planning systemd migration. triggers: - "cron evaluation" - "cron audit" - "cron health" - "collision detection" - "cron optimization" - "systemd migration" author: "Axioma Cluster" date: "2026-05-17" version: 1.0.0 tags: - cron - evaluation - system-administration - performance - systemd - scheduling status: "active" --- # Cron Evaluator Evaluates cron job health across 4 pillars: Temporal, Resource, Resilience, and Pertinence. ## 4 Pillars | Pillar | Checks | Score Range | |--------|--------|-------------| | **Temporal** | Midnight collision, frequency, jitter | 0-1 | | **Resource** | Heavy commands (torch, docker, ollama) | 0-1 | | **Resilience** | Logging, flock, timeout, error handling | 0-1 | | **Pertinence** | Cron vs systemd timer recommendation | 0-1 | ## Usage ```bash # Scan all cron jobs python3 cron_evaluator_v3.py --scan # Run health check python3 cron_evaluator_v3.py --health # Get improvement suggestions python3 cron_evaluator_v3.py --suggest ``` ## Health Thresholds | Score | Status | |-------|--------| | >80% | 🟢 HEALTHY | | 60-80% | 🟡 NEEDS_WORK | | <60% | 🔴 CRITICAL | ## Key Checks ### Temporal Collision Detection ```python # Two crons at 00:00 → suggest jitter if cron1['time'] == '0 0 * * *' and cron2['time'] == '0 0 * * *': suggest("Add jitter: sleep $((RANDOM % 60))") ``` ### Resource Signatures ```python HEAVY_COMMANDS = ['torch', 'tensorflow', 'ollama', 'docker'] if any(h in command for h in HEAVY_COMMANDS): score -= 0.3 # Heavy resource consumer ``` ### Resilience Verification ```python CHECKS = { 'logging': '>> /var/log/...' in command or '2>' in command, 'flock': 'flock' in command, 'timeout': 'timeout' in command, 'error_handling': '||' in command } ``` ## Jitter Implementation ```bash # BEFORE (collision risk) 0 2 * * * /path/to/script.sh # AFTER (staggered with jitter) 0 2 * * * sleep $((RANDOM % 60)) && /path/to/script.sh ``` ## Wrapper for Observability ```bash # Wrapper that logs time/exit/memory 0 2 * * * /usr/bin/python3 /opt/evaluator/wrapper.py --task "TaskName" --cmd "/path/to/script.sh" ``` ## Files ``` cron-evaluator/ ├── SKILL.md # This file ├── scripts/ │ ├── cron_evaluator.py # v1 (basic) │ ├── cron_evaluator_v2.py # v2 (enhanced) │ ├── cron_evaluator_v3.py # v3 (with KAN) │ └── train_cron_kan.py # KAN training script ├── data/ │ └── cron_training.json # Training dataset └── models/ └── cron_kan.pt # Trained KAN model ``` ## KAN Model The Cron KAN (16→32→16→8→4→3) predicts cron job quality from 16 features extracted from schedule and command patterns.

path: data/cron_training.json
content: [
  {"schedule": ["0", "2", "*", "*", "*"], "command": "python watchdog.py", "features": [0,0,3,1,0,0,0,0,0,1,0,0,0.5,1,0,0], "label": 1, "quality": 0.73},
  {"schedule": ["*", "*", "*", "*", "*"], "command": "python script.py", "features": [1,0,0,1,0,0,0,0,0,0,0,0,0.4,1,0,1], "label": 0, "quality": 0.35},
  {"schedule": ["0", "0", "*", "*", "*"], "command": "cd /app && python heavy.py", "features": [0,0,4,1,1,0,0,0,0,0,0,0,0.7,1,1,0], "label": 0, "quality": 0.25},
  {"schedule": ["0", "8", "*", "*", "*"], "command": "flock /tmp/lock python script.py", "features": [0,0,4,1,0,0,0,1,0,0,0,0,0.6,0,0,0], "label": 1, "quality": 0.79},
  {"schedule": ["0", "0", "*", "*", "0"], "command": "python test.py >> /var/log/test.log", "features": [0,0,4,0,0,0,1,0,0,1,1,0,0.5,1,0,0], "label": 1, "quality": 0.82},
  {"schedule": ["*/15", "*", "*", "*", "*"], "command": "python checker.py", "features": [0,1,1,1,0,0,0,0,0,0,0,0,0.4,0,0,0], "label": 0, "quality": 0.45},
  {"schedule": ["0", "*/6", "*", "*", "*"], "command": "cd /app && flock /tmp/lock python auto.py", "features": [0,0,4,1,0,0,0,1,0,0,1,0,0.6,0,1,0], "label": 1, "quality": 0.81},
  {"schedule": ["*", "*", "*", "*", "*"], "command": "flock /tmp/l heavy.py", "features": [1,0,0,1,1,0,0,1,0,0,0,0,0.8,1,0,0], "label": 1, "quality": 0.68},
  {"schedule": ["0", "0", "*", "*", "*"], "command": "python main.py", "features": [0,0,4,1,0,0,0,0,0,0,0,0,0.5,1,0,0], "label": 0, "quality": 0.42},
  {"schedule": ["0", "2", "*", "*", "0"], "command": "flock /tmp/a python adversarial.py", "features": [0,0,5,1,0,0,0,1,0,0,0,0,0.7,1,0,0], "label": 1, "quality": 0.79},
  {"schedule": ["*/5", "*", "*", "*", "*"], "command": "python cron.py >> /var/log/cron.log 2>&1", "features": [0,1,1,1,0,0,1,0,0,1,0,0,0.4,0,0,0], "label": 1, "quality": 0.75},
  {"schedule": ["0", "0", "*", "*", "*"], "command": "python backup.py 2>&1 | tee /var/log/backup.log", "features": [0,0,4,1,0,0,1,0,0,1,1,0,0.8,1,0,0], "label": 1, "quality": 0.88},
  {"schedule": ["*/30", "*", "*", "*", "*"], "command": "python health.py", "features": [0,0,2,1,0,0,0,0,0,0,0,0,0.3,0,0,0], "label": 1, "quality": 0.55},
  {"schedule": ["0", "12", "*", "*", "*"], "command": "flock -n /tmp/lock python task.py >> /var/log/task.log", "features": [0,0,4,1,0,0,0,1,1,1,0,0,0.6,0,0,0], "label": 1, "quality": 0.85},
  {"schedule": ["*", "*", "*", "*", "*"], "command": "heavy", "features": [1,0,0,1,1,0,0,0,0,0,0,0,0.2,1,1,1], "label": 0, "quality": 0.22},
  {"schedule": ["0", "3", "*", "*", "*"], "command": "cd /scripts && flock /tmp/morning.lock python morning.py", "features": [0,0,5,1,0,0,0,1,0,0,1,0,0.6,1,1,0], "label": 1, "quality": 0.83},
  {"schedule": ["0", "0", "*", "*", "*"], "command": "systemctl start service", "features": [0,0,4,0,0,0,0,0,0,0,0,1,0.4,1,0,0], "label": 0, "quality": 0.35},
  {"schedule": ["0", "4", "*", "*", "*"], "command": "python backup.py >> /var/log/backup.log 2>&1", "features": [0,0,4,1,0,0,1,0,0,1,1,0,0.7,1,0,0], "label": 1, "quality": 0.87},
  {"schedule": ["*/10", "*", "*", "*", "*"], "command": "flock /tmp/check.lock python check.py", "features": [0,1,1,1,0,0,0,1,0,0,0,0,0.5,0,0,0], "label": 1, "quality": 0.72},
  {"schedule": ["0", "5", "*", "*", "*"], "command": "python sync.py", "features": [0,0,4,1,0,0,0,0,0,0,0,0,0.4,1,0,0], "label": 1, "quality": 0.60}
]


path: scripts/cron_evaluator.py
content: #!/usr/bin/env python3
"""
🕐 CRON EVALUATOR — Axioma Cluster
===================================
Analyse les crons pour: collisions, ressources, résilience, pertinence

Usage:
    python3 cron_evaluator.py --scan
    python3 cron_evaluator.py --health
    python3 cron_evaluator.py --suggest
"""

import argparse
import sys
import os
import re
from datetime import datetime, timedelta
from pathlib import Path


class CronEvaluator:
    """Évalue les crons selon 4 piliers"""
    
    def __init__(self):
        self.pillars = {
            'temporal': 'Détection collisions',
            'resource': 'Signature RAM/CPU',
            'resilience': 'Error handling + logs',
            'pertinence': 'Cron vs systemd vs webhook'
        }
    
    def scan_crontab(self):
        """Scan la crontab actuelle"""
        try:
            result = os.popen('crontab -l 2>/dev/null').read()
            return result if result else ""
        except:
            return ""
    
    def parse_cron_line(self, line):
        """Parse une ligne cron"""
        if not line or line.strip().startswith('#'):
            return None
        
        parts = line.strip().split()
        if len(parts) < 6:
            return None
        
        schedule = parts[:5]
        command = ' '.join(parts[5:])
        
        return {
            'schedule': schedule,
            'command': command,
            'raw': line
        }
    
    def calculate_frequency(self, schedule):
        """Calcule la fréquence d'exécution"""
        minute, hour, day, month, dow = schedule
        
        # Simplified frequency calculation
        if minute.startswith('*/'):
            interval = int(minute[2:])
            return f"every {interval} min"
        elif minute == '*':
            return "every minute"
        elif hour == '*':
            return "hourly"
        elif day == '*' and month == '*':
            return "daily"
        else:
            return "custom"
    
    def detect_temporal_collision(self, cron1, cron2):
        """Détecte collision temporelle"""
        # Simplified: both at 00:00
        if cron1['schedule'][0] == '0' and cron2['schedule'][0] == '0':
            if cron1['schedule'][1] == '0' and cron2['schedule'][1] == '0':
                return True
        return False
    
    def evaluate_pillar(self, cron, pillar):
        """Évalue un pilier"""
        score = 1.0
        issues = []
        
        if pillar == 'temporal':
            # Check if frequent cron on resource-heavy task
            if '*' in cron['schedule'][0] and 'python' in cron['command']:
                score = 0.6
                issues.append("Python script every minute = risk")
            
        elif pillar == 'resource':
            # Check for heavy commands
            heavy = ['torch', 'tensorflow', 'ollama', 'docker']
            if any(h in cron['command'] for h in heavy):
                score = 0.7
                issues.append("Heavy command detected")
        
        elif pillar == 'resilience':
            # Check for logging
            if '>>' not in cron['command'] and '2>' not in cron['command']:
                score = 0.6
                issues.append("No logging redirect")
            
            # Check for flock
            if 'flock' not in cron['command']:
                score *= 0.8
                issues.append("No flock lock")
        
        elif pillar == 'pertinence':
            # Check if should be systemd
            if 'service' in cron['command'] or 'systemctl' in cron['command']:
                score = 0.5
                issues.append("Should be systemd, not cron")
        
        return {'score': score, 'issues': issues}
    
    def evaluate_cron(self, cron):
        """Évalue un cron complet"""
        results = {}
        for pillar in self.pillars:
            results[pillar] = self.evaluate_pillar(cron, pillar)
        
        # Health score
        health = sum(r['score'] for r in results.values()) / len(results)
        
        return {
            'cron': cron,
            'pillars': results,
            'health': health,
            'frequency': self.calculate_frequency(cron['schedule'])
        }
    
    def scan_all(self):
        """Scan tous les crons"""
        crontab = self.scan_crontab()
        lines = crontab.split('\n')
        
        crons = []
        for line in lines:
            parsed = self.parse_cron_line(line)
            if parsed:
                crons.append(parsed)
        
        # Evaluate each
        results = []
        for cron in crons:
            result = self.evaluate_cron(cron)
            results.append(result)
        
        # Overall health
        overall = sum(r['health'] for r in results) / len(results) if results else 0
        
        return {
            'total': len(results),
            'results': results,
            'overall_health': overall
        }


def main():
    parser = argparse.ArgumentParser(description='Cron Evaluator')
    parser.add_argument('--scan', action='store_true', help='Scan crontab')
    parser.add_argument('--health', action='store_true', help='Show health score')
    parser.add_argument('--suggest', action='store_true', help='Suggest optimizations')
    args = parser.parse_args()
    
    evaluator = CronEvaluator()
    
    if args.scan or args.health or args.suggest:
        result = evaluator.scan_all()
        
        print(f"""
╔═══════════════════════════════════════════════════════════╗
║  🕐 CRON EVALUATOR — Analyse Systémique             ║
╠═══════════════════════════════════════════════════════════╣
║  Total crons: {result['total']}
║  Health Score: {result['overall_health']*100:.0f}%
╚═══════════════════════════════════════════════════════════╝
        """)
        
        for r in result['results']:
            cron = r['cron']
            print(f"\n📋 Command: {cron['command'][:50]}...")
            print(f"   Frequency: {r['frequency']}")
            print(f"   Health: {r['health']*100:.0f}%")
            for pillar, data in r['pillars'].items():
                if data['issues']:
                    print(f"   ⚠️ {pillar}: {', '.join(data['issues'])}")
    
    return 0


if __name__ == "__main__":
    sys.exit(main() or 0)


path: scripts/train_cron_kan.py
content: #!/usr/bin/env python3
"""
🕐 CRON KAN TRAINER
===================
Trains the Cron Evaluator KAN

Architecture: 16 → 32 → 16 → 8 → 4 → 3
Input: 16 features (cron quality metrics)
Output: 3 classes (BAD=0, OK=1, GOOD=2)
"""

import os
import json
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import numpy as np
from pathlib import Path

MODEL_DIR = "/mnt/Morgana/skills/cron-evaluator/models"
DATA_DIR = "/mnt/Morgana/skills/cron-evaluator/data"
os.makedirs(MODEL_DIR, exist_ok=True)


class CronKANNet(nn.Module):
    """KAN for cron quality prediction - 16→32→16→8→4→3"""
    
    def __init__(self, input_dim=16, output_dim=3):
        super().__init__()
        
        self.layers = nn.ModuleList([
            nn.Linear(input_dim, 32),
            nn.Linear(32, 16),
            nn.Linear(16, 8),
            nn.Linear(8, 4),
            nn.Linear(4, output_dim)
        ])
        
        self.bn_layers = nn.ModuleList([
            nn.BatchNorm1d(32),
            nn.BatchNorm1d(16),
            nn.BatchNorm1d(8),
            nn.BatchNorm1d(4)
        ])
        
        self.activation = nn.ReLU()
        self.dropout = nn.Dropout(0.1)
        self.output_activation = nn.Sigmoid()
    
    def forward(self, x):
        for i, layer in enumerate(self.layers[:-1]):
            x = layer(x)
            x = self.bn_layers[i](x)
            x = self.activation(x)
            x = self.dropout(x)
        
        x = self.layers[-1](x)
        return self.output_activation(x)


class CronDataset(Dataset):
    def __init__(self, data):
        self.features = torch.tensor([d["features"] for d in data], dtype=torch.float32)
        self.labels = torch.tensor([d["label"] for d in data], dtype=torch.long)
    
    def __len__(self):
        return len(self.labels)
    
    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]


def train_kan(epochs=500, batch_size=8, lr=0.001):
    """Train the Cron KAN"""
    
    print("=" * 60)
    print("TRAINING CRON KAN (16→32→16→8→4→3)")
    print("=" * 60)
    
    # Load training data
    with open(f"{DATA_DIR}/cron_training.json", 'r') as f:
        data = json.load(f)
    
    print(f"\n📊 Training samples: {len(data)}")
    
    dataset = CronDataset(data)
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    
    # Model
    model = CronKANNet(input_dim=16, output_dim=3)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=150, gamma=0.5)
    
    # Training
    print("\n🚀 Training...")
    for epoch in range(epochs):
        model.train()
        total_loss = 0
        correct = 0
        total = 0
        
        for batch_features, batch_labels in dataloader:
            optimizer.zero_grad()
            outputs = model(batch_features)
            loss = criterion(outputs, batch_labels)
            loss.backward()
            optimizer.step()
            
            total_loss += loss.item()
            _, predicted = torch.max(outputs, 1)
            total += batch_labels.size(0)
            correct += (predicted == batch_labels).sum().item()
        
        scheduler.step()
        
        if (epoch + 1) % 100 == 0:
            accuracy = 100 * correct / total
            print(f"   Epoch {epoch+1}/{epochs} - Loss: {total_loss/len(dataloader):.4f} - Acc: {accuracy:.1f}%")
    
    # Save
    model_path = f"{MODEL_DIR}/cron_kan.pt"
    torch.save({
        'model_state_dict': model.state_dict(),
        'architecture': '16→32→16→8→4→3',
        'input_dim': 16,
        'output_dim': 3,
        'training_samples': len(data),
        'epochs': epochs
    }, model_path)
    
    print(f"\n✅ Model saved to: {model_path}")
    
    # Test
    print("\n🧪 Testing...")
    model.eval()
    with torch.no_grad():
        test_input = torch.randn(1, 16)
        test_output = model(test_input)
        print(f"   Output shape: {test_output.shape}")
        print(f"   Output: {test_output}")
    
    return model


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--epochs", type=int, default=500)
    parser.add_argument("--batch-size", type=int, default=8)
    parser.add_argument("--lr", type=float, default=0.001)
    args = parser.parse_args()
    
    train_kan(epochs=args.epochs, batch_size=args.batch_size, lr=args.lr)


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)