SkyOSFullCore / worklog.py
sky-meilin's picture
Update worklog.py
3da42cc verified
Raw
History Blame Contribute Delete
11.8 kB
from __future__ import annotations
import json
import os
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
WORKLOG_VERSION = "worklog_v1_system_trace_2026_06_10"
WORKLOG_FILE = os.getenv("SKYOS_WORKLOG_FILE", "skyos_worklog.jsonl")
DEFAULT_RECOVERY_RULES = [
{
"rule_id": "no_append_fixes_core_python",
"title": "Keine Append-Fixes bei Python-Core-Dateien",
"description": (
"Core-Dateien werden nicht durch Anhängen von Codeblöcken repariert. "
"Änderungen erfolgen als vollständige Datei oder als klar definierter Austauschblock."
),
"applies_to": [
"app.py",
"router.py",
"router_publish.py",
"publish_gateway.py",
"sheets_gateway.py",
"crm_core.py",
],
"reason": "Append-Fixes können doppelte Funktionen, leere def-Blöcke oder Syntaxfehler erzeugen.",
"status": "active",
},
{
"rule_id": "split_large_router_files",
"title": "Große Router-Dateien auslagern",
"description": (
"Neue Funktionsbereiche werden in zweite Router oder Gateway-Dateien ausgelagert, "
"statt router.py immer weiter aufzublähen."
),
"applies_to": [
"router.py",
"router_publish.py",
],
"reason": "Zu große Dateien sind auf Handy/Hugging-Face-Editor fehleranfällig.",
"status": "active",
},
{
"rule_id": "safe_import_fallback",
"title": "Module dürfen den Haupt-Router nicht abstürzen lassen",
"description": (
"Optionale Module werden mit try/except geladen. Wenn ein Modul kaputt ist, "
"muss der Router trotzdem starten und den Fehler im Status anzeigen."
),
"applies_to": [
"router.py",
"router_publish.py",
],
"reason": "Ein kaputtes Zusatzmodul darf nicht das ganze SkyOS stoppen.",
"status": "active",
},
]
# ============================================================
# CORE HELPERS
# ============================================================
def now_iso() -> str:
# KORRIGIERT: Python 3.13 konforme Zeitzonen-Generierung zur Vermeidung von Build-Warnungen
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def safe_str(value: Any) -> str:
if value is None:
return ""
return str(value)
def normalize_entry(entry: Dict[str, Any]) -> Dict[str, Any]:
return {
"timestamp": entry.get("timestamp") or now_iso(),
"action": entry.get("action", "unknown_action"),
"module": entry.get("module", "unknown_module"),
"file": entry.get("file"),
"reason": entry.get("reason"),
"result": entry.get("result"),
"error": entry.get("error"),
"recovery_action": entry.get("recovery_action"),
"next_step": entry.get("next_step"),
"risk_level": entry.get("risk_level", "low"),
"status": entry.get("status", "logged"),
"metadata": entry.get("metadata", {}),
}
def append_worklog_entry(entry: Dict[str, Any]) -> Dict[str, Any]:
normalized = normalize_entry(entry)
try:
with open(WORKLOG_FILE, "a", encoding="utf-8") as file:
file.write(json.dumps(normalized, ensure_ascii=False) + "\n")
return {
"status": "success",
"module": "worklog",
"action": "append_worklog_entry",
"file": WORKLOG_FILE,
"entry": normalized,
}
except Exception as exc:
return {
"status": "error",
"module": "worklog",
"action": "append_worklog_entry",
"file": WORKLOG_FILE,
"message": str(exc),
"entry": normalized,
}
def read_worklog_entries(limit: int = 20) -> Dict[str, Any]:
if limit <= 0:
limit = 20
if not os.path.exists(WORKLOG_FILE):
return {
"status": "success",
"module": "worklog",
"action": "read_worklog_entries",
"file": WORKLOG_FILE,
"count": 0,
"entries": [],
"message": "Noch keine Worklog-Datei vorhanden.",
}
entries: List[Dict[str, Any]] = []
try:
with open(WORKLOG_FILE, "r", encoding="utf-8") as file:
lines = file.readlines()
for line in lines[-limit:]:
line = line.strip()
if not line:
continue
try:
entries.append(json.loads(line))
except Exception:
entries.append({
"timestamp": now_iso(),
"action": "read_malformed_entry",
"module": "worklog",
"status": "warning",
"raw": line,
})
return {
"status": "success",
"module": "worklog",
"action": "read_worklog_entries",
"file": WORKLOG_FILE,
"count": len(entries),
"entries": entries,
}
except Exception as exc:
return {
"status": "error",
"module": "worklog",
"action": "read_worklog_entries",
"file": WORKLOG_FILE,
"message": str(exc),
"entries": [],
}
def get_last_worklog_entry() -> Dict[str, Any]:
result = read_worklog_entries(limit=1)
entries = result.get("entries", [])
if not entries:
return {
"status": "empty",
"module": "worklog",
"message": "Kein Worklog-Eintrag vorhanden.",
}
return {
"status": "success",
"module": "worklog",
"entry": entries[-1],
}
# ============================================================
# SPECIALIZED LOGGING
# ============================================================
def log_router_recovery(
error: str,
recovery_action: str,
result: str,
next_step: str,
file: str = "router.py",
) -> Dict[str, Any]:
return append_worklog_entry({
"action": "router_recovery",
"module": "router",
"file": file,
"reason": "Router oder Zusatzrouter wurde durch Fehler instabil.",
"error": error,
"recovery_action": recovery_action,
"result": result,
"next_step": next_step,
"risk_level": "medium",
"status": "recovered",
})
def log_file_change(
file: str,
reason: str,
result: str,
next_step: Optional[str] = None,
module: str = "skyos",
risk_level: str = "low",
) -> Dict[str, Any]:
return append_worklog_entry({
"action": "file_change",
"module": module,
"file": file,
"reason": reason,
"result": result,
"next_step": next_step,
"risk_level": risk_level,
"status": "changed",
})
def log_import_error(
module: str,
file: str,
error: str,
recovery_action: str,
next_step: Optional[str] = None,
) -> Dict[str, Any]:
return append_worklog_entry({
"action": "import_error",
"module": module,
"file": file,
"reason": "Modul konnte nicht geladen werden.",
"error": error,
"recovery_action": recovery_action,
"result": "Fehler wurde protokolliert und kann im Status sichtbar gemacht werden.",
"next_step": next_step,
"risk_level": "medium",
"status": "warning",
})
def log_decision(
decision: str,
reason: str,
next_step: Optional[str] = None,
module: str = "skyos",
) -> Dict[str, Any]:
return append_worklog_entry({
"action": "decision",
"module": module,
"file": None,
"reason": reason,
"result": decision,
"next_step": next_step,
"risk_level": "low",
"status": "decided",
})
# ============================================================
# STATUS / TRACE OUTPUT
# ============================================================
def get_recovery_rules() -> Dict[str, Any]:
return {
"status": "success",
"module": "worklog",
"version": WORKLOG_VERSION,
"rules": DEFAULT_RECOVERY_RULES,
"count": len(DEFAULT_RECOVERY_RULES),
}
def format_entry(entry: Dict[str, Any]) -> str:
lines = [
f"- Zeit: {entry.get('timestamp')}",
f"- Aktion: {entry.get('action')}",
f"- Modul: {entry.get('module')}",
]
if entry.get("file"):
lines.append(f"- Datei: {entry.get('file')}")
if entry.get("reason"):
lines.append(f"- Grund: {entry.get('reason')}")
if entry.get("error"):
lines.append(f"- Fehler: {entry.get('error')}")
if entry.get("recovery_action"):
lines.append(f"- Recovery: {entry.get('recovery_action')}")
if entry.get("result"):
lines.append(f"- Ergebnis: {entry.get('result')}")
if entry.get("next_step"):
lines.append(f"- Nächster Schritt: {entry.get('next_step')}")
return "\n".join(lines)
def get_worklog_status(limit: int = 5) -> str:
entries_result = read_worklog_entries(limit=limit)
entries = entries_result.get("entries", [])
lines = [
f"Worklog Version: {WORKLOG_VERSION}",
"",
"Worklog Status:",
f"- Datei: {WORKLOG_FILE}",
f"- Einträge geladen: {len(entries)}",
f"- Speicherstatus: {entries_result.get('status')}",
"",
"Aktive Arbeitsregeln:",
]
for rule in DEFAULT_RECOVERY_RULES:
lines.append(f"- {rule.get('title')}")
lines.append("")
lines.append("Letzte Arbeitsschritte:")
if not entries:
lines.append("- Noch keine Einträge vorhanden.")
else:
for entry in entries:
lines.append("")
lines.append(format_entry(entry))
return "\n".join(lines)
def handle_worklog_request(message: str) -> Dict[str, Any]:
text = safe_str(message).lower().strip()
if "regel" in text or "rules" in text or "arbeitsregel" in text:
rules = get_recovery_rules()
return {
"status": "success",
"module": "worklog",
"mode": "rules",
"rules": rules,
"response": get_worklog_status(limit=3),
}
if "letzte" in text or "trace" in text or "worklog" in text or "arbeitsweise" in text:
return {
"status": "success",
"module": "worklog",
"mode": "status",
"response": get_worklog_status(limit=5),
}
return {
"status": "success",
"module": "worklog",
"mode": "default",
"response": get_worklog_status(limit=5),
}
def seed_current_recovery_context() -> Dict[str, Any]:
return append_worklog_entry({
"action": "system_recovery_rule_created",
"module": "worklog",
"file": "worklog.py",
"reason": (
"Vorherige Core-Dateien wurden durch Append-Fixes und zu große Router-Dateien instabil."
),
"error": (
"IndentationError in router.py und publish_gateway.py durch unvollständige oder angehängte Codeblöcke."
),
"recovery_action": (
"Router wurde in Haupt-Router und Publish-Router aufgeteilt; "
"Core-Dateien sollen künftig vollständig ersetzt oder kontrolliert blockweise geändert werden."
),
"result": "Arbeitsweise wird ab jetzt nachvollziehbar protokolliert.",
"next_step": "worklog.py in Router-Status oder eigenen Worklog-Intent einbinden.",
"risk_level": "medium",
"status": "seeded",
})
if __name__ == "__main__":
print(get_worklog_status())