diff --git a/adaptive_runtime/core/confidence_engine.py.bak b/adaptive_runtime/core/confidence_engine.py.bak new file mode 100644 index 0000000..5917d56 --- /dev/null +++ b/adaptive_runtime/core/confidence_engine.py.bak @@ -0,0 +1,110 @@ +""" +Confidence Engine - adaptive probabilistic confidence scoring. +""" + +import math +from collections import deque +from dataclasses import dataclass, field +from typing import Deque + +from pydantic import BaseModel +from ..observability.logger import get_logger +from ..observability.metrics import metrics + +logger = get_logger("confidence_engine") + + +@dataclass +class OutcomeRecord: + success: bool + confidence_at_time: float + context_risk: str + + +class ConfidenceResult(BaseModel): + confidence: float + decay_factor: float + history_weight: float + context_adjustment: float + final: float + + +class ConfidenceEngine: + """ + Calculates adaptive confidence using: + - base score from event severity + - contextual adjustment from risk level + - historical outcome weighting (exponential decay) + - configurable decay over time + """ + + RISK_WEIGHTS = { + "low": 1.00, + "medium": 0.90, + "high": 0.75, + "critical": 0.55, + } + + def __init__( + self, + base_confidence: float = 0.75, + decay_rate: float = 0.05, + history_window: int = 50, + ): + self._base = base_confidence + self._decay_rate = decay_rate + self._history: Deque[OutcomeRecord] = deque(maxlen=history_window) + self._call_count = 0 + + + def calculate(self, event: dict, context_risk: str) -> ConfidenceResult: + self._call_count += 1 + + severity = float(event.get("severity", 0.5)) + base = max(0.1, self._base - severity * 0.2) + + decay = self._decay_factor() + hist_weight = self._history_weight(context_risk) + ctx_adj = self.RISK_WEIGHTS.get(context_risk, 0.8) + + raw = base * decay * hist_weight * ctx_adj + final = round(min(max(raw, 0.05), 0.99), 4) + + result = ConfidenceResult( + confidence=round(base, 4), + decay_factor=round(decay, 4), + history_weight=round(hist_weight, 4), + context_adjustment=round(ctx_adj, 4), + final=final, + ) + metrics.record("confidence.final", final) + logger.info( + "Confidence → base=%.2f decay=%.2f hist=%.2f ctx=%.2f final=%.4f", + base, decay, hist_weight, ctx_adj, final, + ) + return result + + def record_outcome(self, success: bool, confidence: float, context_risk: str) -> None: + """Feed outcome back for adaptive weighting.""" + self._history.append(OutcomeRecord( + success=success, + confidence_at_time=confidence, + context_risk=context_risk, + )) + logger.debug("Outcome recorded: success=%s conf=%.3f", success, confidence) + + + def _decay_factor(self) -> float: + """Confidence decays slightly as call volume grows (simulate drift).""" + return math.exp(-self._decay_rate * (self._call_count // 20)) + + def _history_weight(self, context_risk: str) -> float: + """Adjust weight based on past success rate for the same risk tier.""" + relevant = [r for r in self._history if r.context_risk == context_risk] + if len(relevant) < 3: + return 1.0 + success_rate = sum(1 for r in relevant if r.success) / len(relevant) + # Map [0, 1] success rate → [0.6, 1.1] weight + return 0.6 + success_rate * 0.5 + + diff --git a/adaptive_runtime/core/context_engine.py.bak b/adaptive_runtime/core/context_engine.py.bak new file mode 100644 index 0000000..403d4ef --- /dev/null +++ b/adaptive_runtime/core/context_engine.py.bak @@ -0,0 +1,132 @@ +""" +Context Engine - transforms raw events into contextual understanding. +""" + +from pydantic import BaseModel, Field +from ..observability.logger import get_logger +from ..observability.metrics import metrics + +logger = get_logger("context_engine") + + + +class RawEvent(BaseModel): + type: str + severity: float = 0.0 + cpu: float = 0.0 + memory: float = 0.0 + error_rate: float = 0.0 + latency_ms: float = 0.0 + extra: dict = Field(default_factory=dict) + + +class ContextResult(BaseModel): + risk: str # low | medium | high | critical + stability: str # stable | degraded | low | critical + context: str # symbolic label + pressure_score: float + tags: list[str] + + + +_RISK_RULES: list[tuple[float, str]] = [ + (0.85, "critical"), + (0.60, "high"), + (0.35, "medium"), + (0.00, "low"), +] + +_STABILITY_RULES: list[tuple[float, str]] = [ + (0.85, "critical"), + (0.65, "low"), + (0.40, "degraded"), + (0.00, "stable"), +] + +_CONTEXT_MAP: dict[str, str] = { + "service_overload": "resource_pressure", + "anomaly_detected": "anomaly_signal", + "memory_leak": "resource_pressure", + "timeout": "latency_issue", + "auth_failure": "security_signal", + "recovery_needed": "self_healing", + "degraded_service": "service_degradation", +} + + + +class ContextEngine: + """ + Classifies events and scores runtime context. + + Uses lightweight rule-based scoring - no ML dependencies. + """ + + def __init__(self, custom_context_map: dict[str, str] | None = None): + self._ctx_map = {**_CONTEXT_MAP, **(custom_context_map or {})} + + def analyze(self, event: dict) -> ContextResult: + raw = RawEvent(**{k: v for k, v in event.items() if k in RawEvent.model_fields or k == "extra"}, + extra={k: v for k, v in event.items() if k not in RawEvent.model_fields}) + + pressure = self._pressure_score(raw) + risk = self._classify(pressure, _RISK_RULES) + stability = self._classify(pressure, _STABILITY_RULES) + context_label = self._ctx_map.get(raw.type, f"unknown_{raw.type}") + tags = self._build_tags(raw, risk) + + result = ContextResult( + risk=risk, + stability=stability, + context=context_label, + pressure_score=round(pressure, 4), + tags=tags, + ) + + metrics.record("context.pressure", pressure) + logger.info( + "Context → risk=%s stability=%s ctx=%s pressure=%.2f", + risk, stability, context_label, pressure, + ) + return result + + + def _pressure_score(self, raw: RawEvent) -> float: + """Weighted composite score in [0, 1].""" + weights = { + "severity": 0.35, + "cpu": 0.20, + "memory": 0.20, + "error_rate": 0.15, + "latency_ms": 0.10, + } + score = ( + raw.severity * weights["severity"] + + (raw.cpu / 100) * weights["cpu"] + + (raw.memory / 100) * weights["memory"] + + raw.error_rate * weights["error_rate"] + + min(raw.latency_ms / 5000, 1.0) * weights["latency_ms"] + ) + return min(score, 1.0) + + @staticmethod + def _classify(score: float, rules: list[tuple[float, str]]) -> str: + for threshold, label in rules: + if score >= threshold: + return label + return rules[-1][1] + + @staticmethod + def _build_tags(raw: RawEvent, risk: str) -> list[str]: + tags = [f"risk:{risk}", f"event:{raw.type}"] + if raw.cpu > 80: + tags.append("cpu:high") + if raw.memory > 80: + tags.append("memory:high") + if raw.error_rate > 0.5: + tags.append("errors:elevated") + if raw.latency_ms > 2000: + tags.append("latency:high") + return tags + + diff --git a/adaptive_runtime/core/decision_engine.py.bak b/adaptive_runtime/core/decision_engine.py.bak new file mode 100644 index 0000000..bf9265b --- /dev/null +++ b/adaptive_runtime/core/decision_engine.py.bak @@ -0,0 +1,96 @@ +""" +Decision Engine - generates adaptive runtime decisions. +""" + +from pydantic import BaseModel +from ..observability.logger import get_logger +from ..observability.metrics import metrics + +logger = get_logger("decision_engine") + + +class DecisionResult(BaseModel): + action: str + confidence: float + reason: str + priority: str # low | normal | high | critical + metadata: dict = {} + + +# Each rule: (context_label, risk_level, min_confidence) → action +# Evaluated top-to-bottom; first match wins. + +_RULES: list[tuple[str | None, str | None, float, str, str]] = [ + # (context, risk, min_conf, action, reason) + ("resource_pressure", "critical", 0.0, "scale_up_immediate", "critical_resource_pressure"), + ("resource_pressure", "high", 0.70, "restart_service", "high_resource_pressure"), + ("resource_pressure", "high", 0.0, "throttle_requests", "resource_pressure_low_confidence"), + ("resource_pressure", "medium", 0.0, "optimize_resources", "medium_resource_pressure"), + ("anomaly_signal", "critical", 0.0, "isolate_component", "critical_anomaly"), + ("anomaly_signal", "high", 0.65, "rollback_deployment", "anomaly_with_confidence"), + ("anomaly_signal", "high", 0.0, "increase_monitoring", "anomaly_low_confidence"), + ("anomaly_signal", None, 0.0, "flag_for_review", "anomaly_detected"), + ("latency_issue", "high", 0.0, "optimize_query", "latency_degradation"), + ("latency_issue", None, 0.0, "cache_warmup", "latency_signal"), + ("security_signal", None, 0.0, "trigger_security_audit", "security_event"), + ("self_healing", None, 0.0, "run_recovery", "recovery_requested"), + ("service_degradation","high", 0.0, "circuit_breaker_open", "service_degraded_high"), + ("service_degradation", None, 0.0, "health_check", "service_degraded"), +] + +_PRIORITY_MAP = { + "critical": "critical", + "high": "high", + "medium": "normal", + "low": "low", +} + + +class DecisionEngine: + """ + Matches context + risk + confidence against a rule table to produce + a ranked, explainable action. No ML required. + """ + + def __init__(self, custom_rules: list | None = None): + self._rules = (custom_rules or []) + _RULES + + def decide( + self, + event: dict, + context_label: str, + risk: str, + confidence: float, + ) -> DecisionResult: + action, reason = self._match(context_label, risk, confidence) + priority = _PRIORITY_MAP.get(risk, "normal") + + result = DecisionResult( + action=action, + confidence=round(confidence, 4), + reason=reason, + priority=priority, + metadata={ + "event_type": event.get("type"), + "context": context_label, + "risk": risk, + }, + ) + metrics.record("decision.confidence", confidence) + logger.info( + "Decision → action=%s confidence=%.3f reason=%s priority=%s", + action, confidence, reason, priority, + ) + return result + + + def _match(self, context: str, risk: str, confidence: float) -> tuple[str, str]: + for ctx_rule, risk_rule, min_conf, action, reason in self._rules: + ctx_ok = ctx_rule is None or ctx_rule == context + risk_ok = risk_rule is None or risk_rule == risk + conf_ok = confidence >= min_conf + if ctx_ok and risk_ok and conf_ok: + return action, reason + return "monitor_and_wait", "no_matching_rule" + + diff --git a/adaptive_runtime/runtime/cache.py.bak b/adaptive_runtime/runtime/cache.py.bak new file mode 100644 index 0000000..65c8f48 --- /dev/null +++ b/adaptive_runtime/runtime/cache.py.bak @@ -0,0 +1,52 @@ +""" +Lightweight TTL-based in-memory cache for the runtime. +""" + +import time +from threading import Lock + + +class TTLCache: + """ + Simple thread-safe in-memory cache with per-entry TTL. + Suitable for deduplicating events, caching context results, etc. + """ + + def __init__(self, default_ttl: float = 60.0): + self._store: dict[str, tuple[any, float]] = {} # key → (value, expires_at) + self._default_ttl = default_ttl + self._lock = Lock() + + def set(self, key: str, value, ttl: float | None = None) -> None: + expires = time.monotonic() + (ttl if ttl is not None else self._default_ttl) + with self._lock: + self._store[key] = (value, expires) + + def get(self, key: str): + with self._lock: + entry = self._store.get(key) + if entry is None: + return None + value, expires = entry + if time.monotonic() > expires: + del self._store[key] + return None + return value + + def delete(self, key: str) -> None: + with self._lock: + self._store.pop(key, None) + + def purge_expired(self) -> int: + now = time.monotonic() + with self._lock: + expired = [k for k, (_, exp) in self._store.items() if now > exp] + for k in expired: + del self._store[k] + return len(expired) + + def __len__(self) -> int: + self.purge_expired() + return len(self._store) + + diff --git a/adaptive_runtime/runtime/runtime_manager.py.bak b/adaptive_runtime/runtime/runtime_manager.py.bak new file mode 100644 index 0000000..f3a878b --- /dev/null +++ b/adaptive_runtime/runtime/runtime_manager.py.bak @@ -0,0 +1,184 @@ +""" +Runtime Manager - central orchestrator of all engines. + +Usage: + from adaptive_runtime import Runtime + + runtime = Runtime() + await runtime.start() + result = await runtime.process({"type": "service_overload", "severity": 0.82}) + await runtime.stop() +""" + +import asyncio + +from pydantic import BaseModel + +from ..core.state_engine import StateEngine +from ..core.context_engine import ContextEngine +from ..core.confidence_engine import ConfidenceEngine +from ..core.decision_engine import DecisionEngine +from ..core.recovery_engine import RecoveryEngine + +from .event_bus import EventBus +from .cache import TTLCache + +from ..storage.sqlite_store import SQLiteStore +from ..storage.memory_store import MemoryStore + +from ..observability.logger import get_logger +from ..observability.metrics import metrics + +logger = get_logger("runtime") + + +class RuntimeResult(BaseModel): + event_type: str + context: dict + confidence: float + action: str + reason: str + priority: str + state_persisted: bool + checkpoint_created: bool + + +class Runtime: + """ + Adaptive Runtime - wires all engines together into a single + event-driven processing loop. + + Args: + agent_id: Logical identity of the running agent. + persist: If True (default) use SQLiteStore, else MemoryStore. + checkpoint_every: Create a snapshot every N processed events. + """ + + def __init__( + self, + agent_id: str = "default", + persist: bool = True, + checkpoint_every: int = 10, + ): + self.agent_id = agent_id + self._checkpoint_every = checkpoint_every + self._event_count = 0 + self._started = False + + # Storage + self._store = SQLiteStore() if persist else MemoryStore() + + # Engines + self._state = StateEngine(self._store, agent_id) + self._context = ContextEngine() + self._confidence = ConfidenceEngine() + self._decision = DecisionEngine() + self._recovery = RecoveryEngine(self._store, agent_id) + + # Supporting components + self.bus = EventBus() + self._cache = TTLCache(default_ttl=30.0) + + + async def start(self) -> None: + """Connect storage and restore any previous state.""" + await self._store.connect() + await self._state.load_state() + self._started = True + logger.info("Runtime started agent_id='%s'", self.agent_id) + + async def stop(self) -> None: + await self._store.close() + self._started = False + logger.info("Runtime stopped agent_id='%s'", self.agent_id) + + + async def process(self, event: dict) -> RuntimeResult: + """ + Full pipeline: + Event → Context → Confidence → Decision → State → Recovery + """ + if not self._started: + await self.start() + + etype = event.get("type", "unknown") + logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + logger.info("Event received: %s", etype) + + self._event_count += 1 + metrics.record("runtime.events", 1) + + # 1. Context analysis + ctx_result = self._context.analyze(event) + logger.info( + "[Context Engine] risk=%s stability=%s pressure=%.2f", + ctx_result.risk, ctx_result.stability, ctx_result.pressure_score, + ) + + # 2. Confidence evaluation + conf_result = self._confidence.calculate(event, ctx_result.risk) + logger.info("[Confidence Engine] confidence=%.4f", conf_result.final) + + # 3. Decision + decision = self._decision.decide( + event, + ctx_result.context, + ctx_result.risk, + conf_result.final, + ) + logger.info("[Decision Engine] ACTION: %s", decision.action.upper()) + + # 4. State persistence + patch = { + "last_event": etype, + "last_action": decision.action, + "last_risk": ctx_result.risk, + "last_confidence": conf_result.final, + } + await self._state.patch_state(patch) + logger.info("[State Engine] State persisted") + + # Log event to store + await self._store.log_event( + self.agent_id, etype, event, + outcome={"action": decision.action, "confidence": conf_result.final}, + ) + + # 5. Checkpoint (periodic) + checkpoint_created = False + if self._event_count % self._checkpoint_every == 0: + await self._recovery.create_checkpoint(self._state.current) + checkpoint_created = True + logger.info("[Recovery Engine] Checkpoint created (#%d)", self._event_count) + + # Publish to bus (non-blocking, best-effort) + asyncio.create_task( + self.bus.publish({**event, "_decision": decision.model_dump()}) + ) + + result = RuntimeResult( + event_type=etype, + context=ctx_result.model_dump(), + confidence=conf_result.final, + action=decision.action, + reason=decision.reason, + priority=decision.priority, + state_persisted=True, + checkpoint_created=checkpoint_created, + ) + logger.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n") + return result + + + async def recover(self) -> dict: + """Manually trigger crash recovery.""" + return await self._recovery.crash_recovery(self._state) + + + def metrics_summary(self) -> dict: + return metrics.summary() + + async def event_history(self, limit: int = 20) -> list[dict]: + return await self._store.recent_events(self.agent_id, limit) + + diff --git a/diagnose.py b/diagnose.py new file mode 100644 index 0000000..adfa24d --- /dev/null +++ b/diagnose.py @@ -0,0 +1,53 @@ +""" +diagnose.py +----------- +Prints hex bytes around corrupt characters in the source files. +Run from project root: + python diagnose.py +""" +import os + +FILES = [ + "adaptive_runtime/core/confidence_engine.py", + "adaptive_runtime/runtime/runtime_manager.py", +] + +for fpath in FILES: + if not os.path.exists(fpath): + print(f"NOT FOUND: {fpath}") + continue + + with open(fpath, "rb") as f: + raw = f.read() + + print(f"\n{'='*60}") + print(f"FILE: {fpath}") + print(f"Size: {len(raw)} bytes") + + # Print first non-ASCII sequence found + i = 0 + shown = 0 + while i < len(raw) and shown < 5: + if raw[i] > 0x7f: + start = max(0, i - 4) + chunk = raw[start:i+12] + try: + context = raw[max(0,i-20):i+20].decode("cp1252", errors="replace") + except Exception: + context = "?" + print(f" pos={i:5d} hex={chunk.hex(' ')}") + print(f" context (cp1252): {repr(context)}") + shown += 1 + i += 3 + else: + i += 1 + + if shown == 0: + print(" No non-ASCII bytes found — file may already be clean UTF-8") + # Show a sample of bytes around known keywords + for keyword in [b"arrow", b"Event", b"Confidence", b"base="]: + idx = raw.find(keyword) + if idx >= 0: + chunk = raw[max(0,idx-5):idx+20] + print(f" '{keyword.decode()}' at pos {idx}: {chunk.hex(' ')}") + break diff --git a/fix_encoding.py b/fix_encoding.py new file mode 100644 index 0000000..0b10dc1 --- /dev/null +++ b/fix_encoding.py @@ -0,0 +1,222 @@ +""" +fix_encoding.py v3 +-------------------- +Fixes mojibake in adaptive_runtime source files. + +Two corruption patterns found in this repo: + + Pattern A — ÔåÆ (arrow →, U+2192) + This is UTF-8 bytes E2 86 92 mis-read as cp1252, + then written back as those 3 cp1252 chars. + The file now contains the cp1252 bytes for Ô å Æ. + cp1252: D4 E5 C6 (or nearby variant) + + Pattern B — ├óÔÇØ┬ü (box drawing ═, U+2550) + UTF-8 bytes E2 95 90 mis-read as cp1252. + +Strategy: open file as cp1252, find the mojibake strings, +replace with correct Unicode, save as UTF-8. + +Run from project root: + python fix_encoding.py +""" + +import os +import shutil + +TARGET_DIR = "adaptive_runtime" + +# --------------------------------------------------------------------------- +# String-level replacements (read file as cp1252, fix, write as UTF-8) +# Key = what you see when file is decoded as cp1252 +# Val = correct Unicode character +# --------------------------------------------------------------------------- +STRING_FIXES = [ + # Pattern A: arrow right → + # When UTF-8 E2 86 92 is decoded as cp1252: â†' + # After a second round of mis-encoding, may appear as ÔåÆ or â†' + ("\u00e2\u0086\u0092", "\u2192"), # â†' (most common form) + ("\u00d4\u00e5\u00c6", "\u2192"), # ÔåÆ (second-round corruption) + # Fallback variants seen in CMD output + ("\u0413\u00f3\u00d4\u00c7\u00e1\u00d4\u00c7\u00d6", "\u2192"), + + # Pattern B: box drawing ═ (U+2550) + # UTF-8 E2 95 90 decoded as cp1252: â• + ("\u00e2\u0095\u0090", "\u2550"), + # CMD shows ├óÔÇØ┬ü — cp1252 bytes for that sequence: + ("\u251c\u00f3\u00d4\u00c7\u00d8\u00b0\u00fc", "\u2550"), + + # Pattern B alt: the 3-byte cp1252 read of E2 95 90 + # E2=â 95=\x95(bullet in cp1252) 90=\x90(unused, often ?) + ("\u00e2\u0095\u0090", "\u2550"), + + # em dash — (U+2014) UTF-8: E2 80 94 + ("\u00e2\u0080\u0094", "\u2014"), + + # box drawing ─ (U+2500) UTF-8: E2 94 80 + ("\u00e2\u0094\u0080", "\u2500"), +] + +# Also do a direct byte-level pass for the ═ separator lines +# Runtime_manager uses a long line of ═ chars; bytes after first fix attempt: +BOX_EQ_BYTE_PATTERNS = [ + # Original corrupt form (before any fix attempt) + b"\xc3\xa2\xe2\x80\xa2\xc2\xac", + # After partial fix attempt, may be stored as these cp1252 bytes + b"\xe2\x95\x90", # actual UTF-8 for ═ — this is already correct, skip +] + + +def fix_via_cp1252(filepath: str) -> int: + """Read as cp1252, apply string replacements, write as UTF-8.""" + try: + with open(filepath, "r", encoding="cp1252") as f: + content = f.read() + except Exception as e: + print(f" WARNING: could not read {filepath} as cp1252: {e}") + return 0 + + original = content + for bad, good in STRING_FIXES: + content = content.replace(bad, good) + + if content != original: + shutil.copy2(filepath, filepath + ".bak") + with open(filepath, "w", encoding="utf-8") as f: + f.write(content) + changed = sum(original.count(bad) for bad, _ in STRING_FIXES) + return max(changed, 1) + return 0 + + +def fix_via_bytes(filepath: str) -> int: + """Byte-level pass for ═ separator lines that survived string pass.""" + with open(filepath, "rb") as f: + raw = f.read() + original = raw + + # After the cp1252 pass the file is UTF-8. + # ÔåÆ in cp1252 is bytes D4 E5 C6 + raw = raw.replace(b"\xd4\xe5\xc6", "\u2192".encode("utf-8")) + # ├óÔÇØ┬ü read as raw cp1252 bytes + raw = raw.replace(b"\xc3\xa2\xe2\x80\xa2\xc2\xac", "\u2550".encode("utf-8")) + # â†' as raw bytes (E2 86 92 misread) + raw = raw.replace(b"\xc3\xa2\xe2\x80\xa0\xe2\x80\x99", "\u2192".encode("utf-8")) + + if raw != original: + with open(filepath, "wb") as f: + f.write(raw) + return raw.count("\u2192".encode()) + raw.count("\u2550".encode()) + return 0 + + +def get_hex_sample(filepath: str, search: bytes = b"\xe2\x86\x92") -> str: + """Return hex of first 6 bytes around search pattern, or first 12 bytes.""" + with open(filepath, "rb") as f: + raw = f.read() + idx = raw.find(search) + if idx >= 0: + start = max(0, idx - 2) + return raw[start:start+9].hex(" ") + # fallback: find any high byte + for i, b in enumerate(raw): + if b > 0x7f: + return raw[i:i+9].hex(" ") + return "(no high bytes found)" + + +def probe_encoding(filepath: str) -> str: + with open(filepath, "rb") as f: + raw = f.read(256) + if raw.startswith(b"\xef\xbb\xbf"): + return "UTF-8 BOM" + try: + raw.decode("utf-8") + return "UTF-8" + except UnicodeDecodeError: + return "non-UTF-8" + + +def main(): + if not os.path.isdir(TARGET_DIR): + print(f"ERROR: '{TARGET_DIR}' not found. Run from project root.") + return + + # --- Diagnostic first --- + print("=" * 60) + print("DIAGNOSTIC: encoding and byte samples") + print("=" * 60) + problem_files = [] + for root, _, files in os.walk(TARGET_DIR): + for fname in sorted(files): + if not fname.endswith(".py"): + continue + fpath = os.path.join(root, fname) + enc = probe_encoding(fpath) + with open(fpath, "rb") as f: + raw = f.read() + has_problem = any(b > 0x7f for b in raw[:50]) or b"\xd4\xe5\xc6" in raw or b"\xc3\xa2" in raw + # Check for known corrupt sequences + corrupt = b"\xd4\xe5\xc6" in raw or b"\xc3\xa2\xe2\x80\xa2\xc2\xac" in raw or b"\xc3\xa2\xe2\x80\xa0" in raw + if corrupt: + problem_files.append(fpath) + sample = get_hex_sample(fpath) + print(f" CORRUPT {os.path.relpath(fpath):50s} {enc} hex={sample}") + else: + print(f" ok {os.path.relpath(fpath):50s} {enc}") + + print() + + if not problem_files: + # Try a different approach: check for ÔåÆ as cp1252-decoded text + print("No known byte patterns found.") + print("Trying cp1252 text scan for ÔåÆ and ├óÔÇØ┬ü ...\n") + problem_files = [] + for root, _, files in os.walk(TARGET_DIR): + for fname in sorted(files): + if not fname.endswith(".py"): + continue + fpath = os.path.join(root, fname) + try: + with open(fpath, "r", encoding="cp1252") as f: + content = f.read() + # Look for any of the corrupt char sequences + if any(bad in content for bad, _ in STRING_FIXES): + problem_files.append(fpath) + print(f" cp1252 corrupt: {os.path.relpath(fpath)}") + except Exception: + pass + + if not problem_files: + print("No corrupt sequences found by any method.") + print("Files may already be clean after previous fix run.") + print("Check GitHub to confirm characters render correctly.") + return + + print(f"\nFixing {len(problem_files)} file(s)...\n") + total_fixed = 0 + for fpath in problem_files: + n = fix_via_cp1252(fpath) + if n: + print(f" FIXED (string pass): {os.path.relpath(fpath)} ({n} replacements)") + total_fixed += n + n2 = fix_via_bytes(fpath) + if n2: + print(f" FIXED (byte pass): {os.path.relpath(fpath)} ({n2} replacements)") + total_fixed += n2 + + print() + print("=" * 60) + if total_fixed: + print(f"Done. {total_fixed} replacement(s) made.") + print("Originals backed up as .bak") + print() + print("Verify:") + print(r' findstr /s /n "Ô" adaptive_runtime\*.py adaptive_runtime\*\*.py') + print("Expected: no output") + else: + print("No replacements made. Files may already be clean.") + + +if __name__ == "__main__": + main() diff --git a/verify.py b/verify.py new file mode 100644 index 0000000..0f791ff --- /dev/null +++ b/verify.py @@ -0,0 +1,40 @@ +""" +verify.py - restore remaining .bak files and verify all UTF-8 clean +Run: python verify.py +""" +import os +import shutil + +FILES = [ + "adaptive_runtime/core/confidence_engine.py", + "adaptive_runtime/core/context_engine.py", + "adaptive_runtime/core/decision_engine.py", + "adaptive_runtime/runtime/cache.py", + "adaptive_runtime/runtime/runtime_manager.py", +] + +print("Step 1 — Restore .bak files (skip if no .bak exists)") +for f in FILES: + bak = f + ".bak" + if os.path.exists(bak): + shutil.copy2(bak, f) + print(f" restored: {f}") + else: + print(f" no bak: {f} (skipped)") + +print() +print("Step 2 — Verify all files are valid UTF-8") +all_ok = True +for f in FILES: + try: + open(f, encoding="utf-8").read() + print(f" OK {f}") + except UnicodeDecodeError as e: + print(f" FAIL {f} — {e}") + all_ok = False + +print() +if all_ok: + print("All files clean. Safe to commit to GitHub.") +else: + print("Some files still have issues. Paste output here for next fix.")