#!/usr/bin/env python3 """elon_checklist.py — Trigger settimanale checklist semantica per Elon. Gira via cron (lunedì 04:45). Scrive msg-a2e con la checklist, poi lancia elon_pull --fire-and-forget. Elon risponde alla prima sessione disponibile. maintenance_check.py (09:00) legge il msg-e2a e porta a Mauro solo l'actionable. """ import json import os import subprocess import sys from datetime import datetime, timezone from pathlib import Path import httpx from dotenv import load_dotenv load_dotenv(Path(__file__).parent.parent / ".env") VAULT_DB = Path("/mnt/ssd/data/vault-secondbrain/vault.db") TG_TOKEN = os.getenv("ADRIAN_BOT_TOKEN", "") TG_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "") import sqlite3 def tg_notify(text: str): if not TG_TOKEN or not TG_CHAT_ID: return try: httpx.post( f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage", json={"chat_id": TG_CHAT_ID, "text": text}, timeout=10, ) except Exception: pass CHECKLIST_BODY = """Manutenzione settimanale — checklist semantica. Esegui i controlli sotto su vault.db via MCP. Per ogni punto segui le istruzioni su cosa fare e cosa riportare. ── AUTONOMO (correggi e riporta cosa hai cambiato) ── 1. ALIAS MANCANTI Query: nodi WHERE meta.aliases assente o vuoto. Azione: aggiungi aliases derivato da meta.nome (+ forme abbreviate usate nei wikilink se le conosci). Riporta: lista id modificati. 2. LABEL RELAZIONI VUOTE Query: nodi con relazioni in meta.relazioni dove label="". Azione: aggiungi label dal vocabolario (condizionato_a / poggia_su / collegato_a / eseguito_da / parte_di / ecc.). Riporta: lista src→dst con label aggiunta. 3. LIMBO STANTII (>7 giorni) Query: SELECT id, body, created_at FROM items WHERE type='nodo' AND json_extract(meta,'$.tipo')='limbo' AND json_extract(meta,'$.limbo_stato')='pending' AND created_at < datetime('now','-7 days'). Azione: se il documento è già integrato nel grafo con nodi collegati → delete_item sul limbo. Se non è chiaro → lascia pending e segnala (vedi sezione segnalazioni). Nota: created_at baseline sintetica 2026-07-10 per nodi pre-migrazione — limbo antecedenti quella data potrebbero avere età reale maggiore di 7gg. Riporta: lista eliminati + lista dubbi. ── SEGNALAZIONE (non correggere — riporta solo, Adrian decide) ── 4. BODY TROPPO CORTO Query: nodi type=nodo con len(body) < 30 caratteri. Riporta: lista id + body attuale. 5. TIPO/CONTENUTO INCONGRUENTI Leggi nodi e valuta se il tipo dichiarato è coerente con il body. Esempi da segnalare: episodio che descrive una persona; decisione senza scelta esplicita; filo che è in realtà un cantiere. Riporta: id + tipo attuale + tipo suggerito + motivazione (una riga). 6. EPISODI SENZA DATA Query: nodi meta.tipo=episodio senza meta.data o meta.data_doc valorizzato. Riporta: lista id + body (primi 60 char). 7. CANTIERI POTENZIALMENTE CONCLUSI Query: nodi meta.tipo=cantiere il cui body contiene "completato", "dismesso", "chiuso", "rimosso", "terminato". Riporta: lista id + frase chiave trovata nel body. ── FORMATO RISPOSTA ── Struttura il msg-e2a così: AUTONOMO-FATTO: [lista azioni eseguite, o "nessuno"] SEGNALAZIONI: [lista per categoria, o "nessuno"] Manda msg-e2a quando hai finito. — Adrian""" def send_checklist() -> str: """Scrive msg-a2e con la checklist. Ritorna msg_id.""" ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ") msg_id = f"msg-a2e-{ts}" meta = json.dumps({ "tipo": "agent_memory", "from": "adrian", "to": "elon", "ts": ts, "subject": "checklist-settimanale", }) conn = sqlite3.connect(VAULT_DB) conn.execute( "INSERT OR REPLACE INTO items(id, type, body, file, meta) VALUES (?,?,?,NULL,?)", (msg_id, "os", CHECKLIST_BODY, meta), ) conn.commit() conn.close() return msg_id def main(): ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") print(f"[checklist] Avvio — {ts}") msg_id = send_checklist() print(f"[checklist] msg-a2e scritto: {msg_id}") elon_pull = Path(__file__).parent.parent / "vault-secondbrain" / "elon_pull.py" env = {**os.environ, "DISPLAY": ":0"} result = subprocess.run( [sys.executable, str(elon_pull), "--fire-and-forget"], env=env, capture_output=True, text=True, timeout=120, ) print(result.stdout) if result.returncode != 0: print(f"[checklist] elon_pull fallito (exit {result.returncode}): {result.stderr[:200]}") tg_notify(f"⚠️ elon_checklist: #boot fallito — {result.stderr[:150]}") else: print("[checklist] #boot inviato. Elon risponderà alla prossima sessione.") if __name__ == "__main__": main()