#!/usr/bin/env python3
"""
Genesis Auto-Respawn System — PostToolUse Hook (Layer 6 of Defense System)
==========================================================================
Monitors context usage and executes progressive CTM flush + respawn preparation.

This hook REPLACES the context_tracker.py thresholds with an action-oriented system
that actually performs the flush rather than just warning. It reads from the same
state file (data/context_state/current.json) written by genesis_statusline.sh.

Thresholds (Kinan Directive 2026-02-25):
  50% -> LOG: Warning + prepare CTM (no action, just awareness)
  65% -> CTM + RESPAWN: Execute full CTM flush AND trigger respawn
  70% -> HARD CTM + FORCED RESPAWN: Aggressive flush, mandatory immediate stop

Architecture:
  - StatusLine (Layer 1) writes context_state/current.json on every prompt
  - This hook (Layer 6) reads that file on every PostToolUse event
  - Deduplication via trigger bands (won't re-fire within same 5% band)
  - CTM flush writes to: MEMORY.md, hive/progress/session_N_handoff.md, supermemory
  - Respawn script at scripts/respawn_command_centre.sh handles session restart

Author: Genesis Defense System
Updated: 2026-02-25 — Kinan lowered thresholds: 65% CTM+respawn, 70% hardCTM+respawn
"""

import sys
import json
import os
import subprocess
import re
import time
from datetime import datetime, timezone
from pathlib import Path

# ============================================================================
# CONFIGURATION
# ============================================================================

GENESIS_ROOT = Path("/mnt/e/genesis-system")
STATE_DIR = GENESIS_ROOT / "data" / "context_state"
MEMORY_FILE = Path("/home/authentic88/.claude/projects/-mnt-e-genesis-system/memory/MEMORY.md")
HANDOFF_DIR = GENESIS_ROOT / "hive" / "progress"
SUPERMEMORY_SAVE = GENESIS_ROOT / "mcp-servers" / "supermemory" / "save.sh"
EVENTS_DIR = GENESIS_ROOT / "data" / "observability"
RECOVERY_DIR = GENESIS_ROOT / "hive" / "session_recovery"
RESPAWN_FLAG = STATE_DIR / "respawn_requested.flag"
TRIGGER_LOG = STATE_DIR / "auto_respawn_triggers.jsonl"

# Thresholds (Kinan Directive 2026-02-25: lowered for safety)
WARN_THRESHOLD = 50
CTM_RESPAWN_THRESHOLD = 65      # CTM flush + respawn signal
HARD_RESPAWN_THRESHOLD = 70     # Hard CTM + FORCED immediate respawn

# Deduplication: band size in percentage points
DEDUP_BAND = 5


# ============================================================================
# STATE READING
# ============================================================================

def read_context_state() -> dict:
    """Read context state from StatusLine-generated file."""
    state_file = STATE_DIR / "current.json"
    if not state_file.exists():
        return {}
    try:
        with open(state_file, "r") as f:
            return json.load(f)
    except Exception:
        return {}


def get_session_id() -> str:
    """Get current session ID from state or environment."""
    state = read_context_state()
    sid = state.get("session_id", "")
    if sid:
        return sid
    return os.environ.get("CLAUDE_SESSION_ID", "unknown")


def get_session_number() -> int:
    """Determine session number from existing handoff files."""
    HANDOFF_DIR.mkdir(parents=True, exist_ok=True)
    existing = list(HANDOFF_DIR.glob("session_*_handoff.md"))
    max_n = 0
    for f in existing:
        match = re.search(r"session_(\d+)_handoff\.md", f.name)
        if match:
            n = int(match.group(1))
            if n > max_n:
                max_n = n
    return max_n + 1


def already_triggered(pct: float, action: str) -> bool:
    """Check if this action was already triggered in this band."""
    if not TRIGGER_LOG.exists():
        return False
    band = int(pct / DEDUP_BAND) * DEDUP_BAND
    session_id = get_session_id()
    try:
        with open(TRIGGER_LOG, "r") as f:
            for line in f:
                entry = json.loads(line.strip())
                if (entry.get("action") == action and
                        entry.get("band") == band and
                        entry.get("session_id") == session_id):
                    return True
    except Exception:
        pass
    return False


def log_trigger(pct: float, action: str):
    """Record that we triggered an action at this band."""
    band = int(pct / DEDUP_BAND) * DEDUP_BAND
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    try:
        entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "action": action,
            "percentage": round(pct, 1),
            "band": band,
            "session_id": get_session_id(),
        }
        with open(TRIGGER_LOG, "a") as f:
            f.write(json.dumps(entry) + "\n")
    except Exception:
        pass


# ============================================================================
# AGENT & MISSION STATE GATHERING
# ============================================================================

def get_active_agents() -> dict:
    """Read active agent info from observability metrics."""
    try:
        metrics_file = EVENTS_DIR / "metrics.json"
        if metrics_file.exists():
            with open(metrics_file, "r") as f:
                metrics = json.load(f)
                return {
                    "active_count": metrics.get("agents_active", 0),
                    "total_spawns": metrics.get("agent_spawns", 0),
                    "total_stops": metrics.get("agent_stops", 0),
                }
    except Exception:
        pass
    return {"active_count": 0, "total_spawns": 0, "total_stops": 0}


def get_recent_events(limit: int = 20) -> list:
    """Read recent events from observability log."""
    events = []
    try:
        events_file = EVENTS_DIR / "events.jsonl"
        if events_file.exists():
            with open(events_file, "rb") as f:
                f.seek(0, 2)
                file_size = f.tell()
                read_size = min(file_size, 100000)
                f.seek(max(0, file_size - read_size))
                lines = f.read().decode("utf-8", errors="replace").strip().split("\n")
            for line in lines[-limit:]:
                try:
                    events.append(json.loads(line.strip()))
                except Exception:
                    continue
    except Exception:
        pass
    return events


def get_compaction_count() -> int:
    """Count compaction events."""
    try:
        log_file = STATE_DIR / "compaction_log.jsonl"
        if log_file.exists():
            with open(log_file, "r") as f:
                return sum(1 for _ in f)
    except Exception:
        pass
    return 0


def get_cost() -> float:
    """Get current session cost from state."""
    state = read_context_state()
    return state.get("cost_usd", 0)


# ============================================================================
# CTM FLUSH — THE CORE FUNCTION
# ============================================================================

def execute_ctm_flush(pct: float, trigger_level: str) -> dict:
    """
    Execute the full CTM (Commit To Memory) flush.

    Writes:
    1. Session handoff file to hive/progress/session_N_handoff.md
    2. Saves to supermemory via save.sh (best-effort, non-blocking)
    3. Logs the flush event

    Does NOT modify MEMORY.md directly -- that requires Claude to do it
    via the additionalContext instruction returned by this hook.

    Returns a summary dict of what was written.
    """
    now = datetime.now(timezone.utc)
    session_id = get_session_id()
    session_num = get_session_number()
    agents = get_active_agents()
    context_state = read_context_state()
    compactions = get_compaction_count()
    cost = get_cost()
    recent = get_recent_events(15)

    # ---- 1. Write session handoff file ----
    HANDOFF_DIR.mkdir(parents=True, exist_ok=True)
    handoff_file = HANDOFF_DIR / f"session_{session_num}_handoff.md"

    # Summarize recent tool activity
    tool_summary = []
    for evt in recent[-10:]:
        tool = evt.get("tool", evt.get("event_type", "?"))
        ts = evt.get("timestamp", "?")
        tool_summary.append(f"- {tool} @ {ts}")

    handoff_content = f"""# Session {session_num} Handoff
## Auto-generated by Auto-Respawn System at {trigger_level.upper()} threshold

**Generated**: {now.isoformat()}
**Session ID**: {session_id}
**Context Usage**: {pct:.1f}%
**Remaining (actual)**: {context_state.get('actual_free', '?')}%
**Session Cost**: ${cost:.2f}
**Compaction Events**: {compactions}
**Trigger Level**: {trigger_level} (threshold crossed at {pct:.1f}%)

## Agent Status
- Active agents: {agents.get('active_count', 0)}
- Total spawns this session: {agents.get('total_spawns', 0)}
- Total stops this session: {agents.get('total_stops', 0)}

## Recent Tool Activity (last 10)
{chr(10).join(tool_summary) if tool_summary else "No recent activity recorded."}

## Respawn Instructions
1. Read MEMORY.md -- war room status has the active mission
2. Check hive/progress/ for this and prior handoff files
3. Check hive/session_recovery/LATEST.md for heartbeat state
4. Check data/observability/events.jsonl for full event trail
5. Resume the active mission immediately -- no re-orientation needed

## Context State Snapshot
```json
{json.dumps(context_state, indent=2)}
```

## Auto-Respawn Notes
- This handoff was triggered automatically by the auto-respawn defense system
- The {trigger_level} threshold ({pct:.1f}%) was crossed
- {"RESPAWN WAS REQUESTED -- session should restart automatically" if trigger_level == "respawn" else "Session is still active but approaching limits"}
- Next session should resume from MEMORY.md war room status
"""

    try:
        with open(handoff_file, "w") as f:
            f.write(handoff_content)
    except Exception as e:
        return {"error": f"Failed to write handoff: {e}"}

    # ---- 2. Save to supermemory (best-effort, non-blocking) ----
    supermemory_saved = False
    if SUPERMEMORY_SAVE.exists():
        try:
            summary = (
                f"[CTM-FLUSH session-{session_num}] "
                f"Context at {pct:.1f}%, trigger={trigger_level}, "
                f"cost=${cost:.2f}, agents_active={agents.get('active_count', 0)}, "
                f"compactions={compactions}. "
                f"Handoff at: hive/progress/session_{session_num}_handoff.md"
            )
            subprocess.run(
                ["bash", str(SUPERMEMORY_SAVE), summary, "ctm-flush"],
                timeout=10,
                capture_output=True,
                text=True,
            )
            supermemory_saved = True
        except (subprocess.TimeoutExpired, Exception):
            pass

    # ---- 3. Log the flush event ----
    try:
        EVENTS_DIR.mkdir(parents=True, exist_ok=True)
        event = {
            "timestamp": now.isoformat(),
            "event_type": "ctm_flush",
            "trigger_level": trigger_level,
            "percentage": round(pct, 1),
            "session_id": session_id,
            "session_number": session_num,
            "agents_active": agents.get("active_count", 0),
            "cost_usd": cost,
            "handoff_file": str(handoff_file),
            "supermemory_saved": supermemory_saved,
        }
        events_file = EVENTS_DIR / "events.jsonl"
        with open(events_file, "a") as f:
            f.write(json.dumps(event) + "\n")
    except Exception:
        pass

    return {
        "handoff_file": str(handoff_file),
        "session_number": session_num,
        "supermemory_saved": supermemory_saved,
    }


# ============================================================================
# RESPAWN SIGNAL
# ============================================================================

def write_respawn_flag(pct: float, session_num: int):
    """
    Write a flag file that the respawn script can detect.
    Also writes the respawn command to a known location.
    """
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    try:
        flag_data = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "session_id": get_session_id(),
            "session_number": session_num,
            "context_pct": round(pct, 1),
            "reason": f"Context usage exceeded {RESPAWN_THRESHOLD}%",
        }
        with open(RESPAWN_FLAG, "w") as f:
            json.dump(flag_data, f, indent=2)
    except Exception:
        pass


# ============================================================================
# MAIN HOOK LOGIC
# ============================================================================

def main():
    try:
        hook_input = json.loads(sys.stdin.read())
    except (json.JSONDecodeError, Exception):
        print(json.dumps({}))
        return

    # Read current context state
    state = read_context_state()
    pct = state.get("used_percentage", 0)
    actual_free = state.get("actual_free", 100)

    # If no state file yet or very early in session, skip
    if pct == 0:
        print(json.dumps({}))
        return

    result = {}

    # ====================================================================
    # TIER 3: HARD RESPAWN (70%+) — Forced flush + immediate stop
    # ====================================================================
    if pct >= HARD_RESPAWN_THRESHOLD:
        if not already_triggered(pct, "hard_respawn"):
            log_trigger(pct, "hard_respawn")

            # Execute aggressive full flush
            flush_result = execute_ctm_flush(pct, "hard_respawn")
            session_num = flush_result.get("session_number", "?")

            # Write respawn flag (forced)
            write_respawn_flag(pct, session_num)

            result["additionalContext"] = (
                f"HARD CTM + FORCED RESPAWN: Context at {pct:.0f}% (actual free: {actual_free:.0f}%). "
                f"This is the HARD threshold — session MUST end NOW. "
                f"CTM flush EXECUTED — handoff at hive/progress/session_{session_num}_handoff.md. "
                f"Supermemory: {'saved' if flush_result.get('supermemory_saved') else 'failed (non-critical)'}. "
                f"\n\n"
                f"FORCED STOP PROTOCOL — DO THIS IMMEDIATELY:\n"
                f"1. UPDATE MEMORY.md WAR ROOM STATUS — active mission, agents, decisions, blockers, next actions\n"
                f"2. STOP all background agents — note their IDs\n"
                f"3. DO NOT start any new work — session is dying\n"
                f"4. Tell Kinan: 'Hard CTM complete. Session respawning.'\n"
                f"5. The respawn script is at: scripts/respawn_command_centre.sh\n\n"
                f"This is NON-NEGOTIABLE. The session MUST die and respawn. "
                f"Continuing past 70% risks autocompaction + thinking block corruption."
            )

    # ====================================================================
    # TIER 2: CTM + RESPAWN (65-69%) — Full CTM flush + respawn signal
    # ====================================================================
    elif pct >= CTM_RESPAWN_THRESHOLD:
        if not already_triggered(pct, "ctm_respawn"):
            log_trigger(pct, "ctm_respawn")

            # Execute full flush
            flush_result = execute_ctm_flush(pct, "ctm_respawn")
            session_num = flush_result.get("session_number", "?")

            # Write respawn flag
            write_respawn_flag(pct, session_num)

            result["additionalContext"] = (
                f"CTM + RESPAWN TRIGGERED: Context at {pct:.0f}% (actual free: {actual_free:.0f}%). "
                f"CTM flush EXECUTED — handoff at hive/progress/session_{session_num}_handoff.md. "
                f"Supermemory: {'saved' if flush_result.get('supermemory_saved') else 'failed (non-critical)'}. "
                f"\n\n"
                f"RESPAWN PROTOCOL — BEGIN WRAP-UP:\n"
                f"1. UPDATE MEMORY.md WAR ROOM STATUS — mission, agents, decisions, blockers, next actions\n"
                f"2. Note running agent IDs and expected output paths\n"
                f"3. Wrap current task to a stable checkpoint\n"
                f"4. Tell Kinan: 'CTM complete. Respawn ready at {pct:.0f}%.'\n"
                f"5. Respawn script: scripts/respawn_command_centre.sh\n\n"
                f"Hard CTM + forced respawn triggers at {HARD_RESPAWN_THRESHOLD}%. "
                f"Wrap up NOW — do not wait for the hard threshold."
            )

    # ====================================================================
    # TIER 1: WARN (50-64%) — Log warning, awareness only
    # ====================================================================
    elif pct >= WARN_THRESHOLD:
        if not already_triggered(pct, "warn"):
            log_trigger(pct, "warn")

            result["additionalContext"] = (
                f"CONTEXT AWARENESS: {pct:.0f}% used (actual free: {actual_free:.0f}%). "
                f"CTM + respawn will trigger at {CTM_RESPAWN_THRESHOLD}%. "
                f"Hard CTM + forced respawn at {HARD_RESPAWN_THRESHOLD}%. "
                f"Consider starting to save important state to MEMORY.md proactively."
            )

    print(json.dumps(result))


if __name__ == "__main__":
    main()
