#!/usr/bin/env bash # Pipeline nocturno: JARVIS se mejora solo mientras nadie mira. # # Cada ciclo hace lo mismo: mira los datos que ha dejado el uso real, propone # un cambio, lo aplica, y lo somete a config/puerta.sh. Si la puerta dice que # no, se revierte ENTERO con git y se pasa al siguiente. Nada se queda sin # pasar por ahi. # # Por que git y no una copia: el arbol ya esta versionado, `git checkout` es # atomico y deja rastro de lo que se intento. Al terminar la noche se puede ver # commit a commit que se cambio y por que. # # noche.sh corre hasta que se le diga que pare # noche.sh --ciclos 5 solo cinco vueltas # noche.sh --seco propone y valida, pero NO commitea (para mirar) # # Para pararlo: touch ~/COFRE/CODERS/JARVIS/PARAR # # ── Que puede mejorar solo, y que no ──────────────────────────────────────── # # Se mejora solo lo que se puede VALIDAR solo. Eso es: # · el vocabulario: alias nuevos para ordenes que no se entendieron # · las frases del catalogo: variantes de las que fallan # · la voz: regenerar en la voz buena las frases que cayeron a la de reserva # · el prompt: mantenerlo al dia con el catalogo # · acciones nuevas a partir de lo que se pidio y no existia # # NO se toca solo el motor, el panel ni la logica de la aplicacion. No porque # sea imposible generarlo, sino porque no hay forma automatica de comprobar que # un cambio ahi es bueno: la suite dice si algo se rompio, no si algo mejoro. # Un bucle que reescribe su propio validador acaba pasando siempre. set -uo pipefail # ── DESACTIVADO EL 15 DE AGOSTO DE 2026 ──────────────────────────────────── # # Este bucle modifica el arbol de `newelle/` y COMMITEA solo. Ese arbol esta # congelado en la etiqueta jarvis-newelle-final desde que el desarrollo se # mudo a nucleo/: dejarlo correr escribiria commits automaticos sobre un # repositorio que por definicion ya no cambia. # # Se ha quitado tambien su temporizador de systemd —estaba armado para las # 03:30 y habria saltado solo—, movido a ~/.config/systemd/user/desactivados/. # # Esto de aqui es la segunda cerradura: aunque alguien lo lance a mano, se # niega. Para retomarlo hay que apuntarlo primero al nucleo, y eso no es # cambiar una ruta: la puerta (config/puerta.sh) valida el catalogo, el # generador y la suite VIEJOS, y el nucleo tiene los suyos. if [ "${JARVIS_NOCHE_A_SABIENDAS:-}" != "si" ]; then cat >&2 <<'AVISO' noche.sh esta desactivado. Modifica y commitea sobre newelle/, que esta congelado en jarvis-newelle-final desde que el desarrollo se mudo a nucleo/. Su temporizador de systemd tambien se retiro. Para retomarlo hay que reescribirlo contra el nucleo: la puerta valida el catalogo, el generador y la suite viejos. Si de verdad quieres correrlo tal cual sobre el arbol congelado: JARVIS_NOCHE_A_SABIENDAS=si config/noche.sh AVISO exit 78 fi RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd) cd "$RAIZ" PARADA=$RAIZ/PARAR DIARIO=$RAIZ/noche.log CICLOS=0; SECO=no while [ $# -gt 0 ]; do case "$1" in --ciclos) CICLOS=${2:-0}; shift 2;; --seco) SECO=si; shift;; *) shift;; esac done log() { printf '%s %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$DIARIO"; } # ── salvaguarda ──────────────────────────────────────────────────────────── cd "$RAIZ/newelle" if [ -n "$(git status --porcelain)" ]; then log "ABORTO: el fork tiene cambios sin commitear. Commitealos o descartalos." exit 1 fi ANCLA=$(git rev-parse HEAD) git tag -f "noche-$(date +%Y%m%d-%H%M)" >/dev/null 2>&1 log "ancla: $ANCLA (para volver: git reset --hard $ANCLA)" cd "$RAIZ" # ── las mejoras, cada una en su funcion ──────────────────────────────────── # Cada una devuelve 0 si propuso algo, 1 si no habia nada que hacer. mejora_vocabulario() { # Lo que el usuario dijo y no se entendio. Es la fuente mas valiosa porque # son fallos REALES, no inventados. local registro=$HOME/.var/app/io.github.qwersyk.Newelle/config/jarvis-sin-accion.jsonl [ -f "$registro" ] || return 1 python3 "$RAIZ/config/proponer_alias.py" --aplicar 2>&1 | tail -3 return ${PIPESTATUS[0]} } mejora_frases() { # Variantes de las frases del catalogo, generadas con el modelo local y # filtradas duro. Sube la cobertura sin tocar comandos. python3 "$RAIZ/config/proponer_frases.py" --aplicar 2>&1 | tail -3 return ${PIPESTATUS[0]} } mejora_voz() { # Las frases que cayeron a la voz de reserva se regeneran con la clonada. [ -s "$RAIZ/voz/misses.log" ] || return 1 "$RAIZ/voz/clonador-venv/bin/python" "$RAIZ/voz/calienta_cache.py" cpu 2>&1 | tail -2 return 0 } mejora_prompt() { # El prompt lleva la lista de capacidades: si cambio el catalogo, hay que # regenerarlo o el modelo sigue creyendo que sabe hacer otra cosa. flatpak run --command=python3 io.github.qwersyk.Newelle//master \ "$RAIZ/config/set_persona.py" 2>&1 | grep -v WARNING | tail -1 return 0 } mejora_reconocimiento() { # La unica que genera sus PROPIOS datos: dice las frases del catalogo con # varias voces, velocidades y ruidos, las transcribe con el mismo Whisper # del asistente, y propone alias para lo que no llega a su accion. # # Es la que hace productiva la noche aunque nadie hable con JARVIS. Ya trae # su propio guardarrail —solo aplica si el acierto sube Y la charla sigue # yendo al modelo— y ademas pasa por la puerta como todo lo demas. timeout 3000 python3 "$RAIZ/config/mejora_nocturna.py" --ronda --muestras 2 2>&1 | tail -4 return ${PIPESTATUS[0]} } MEJORAS=(mejora_reconocimiento mejora_vocabulario mejora_frases mejora_prompt mejora_voz) # ── el bucle ─────────────────────────────────────────────────────────────── vuelta=0 aceptados=0; rechazados=0; sin_nada=0 log "=== empieza la noche ===" while true; do [ -f "$PARADA" ] && { log "parada pedida (existe $PARADA)"; break; } [ "$CICLOS" -gt 0 ] && [ "$vuelta" -ge "$CICLOS" ] && { log "hechas $CICLOS vueltas"; break; } vuelta=$((vuelta+1)) for mejora in "${MEJORAS[@]}"; do [ -f "$PARADA" ] && break log "── vuelta $vuelta · $mejora" salida=$($mejora 2>&1); resultado=$? [ -n "$salida" ] && echo "$salida" | sed 's/^/ /' | tee -a "$DIARIO" >/dev/null cd "$RAIZ/newelle" cambios=$(git status --porcelain) cd "$RAIZ" if [ -z "$cambios" ] && [ "$resultado" -ne 0 ]; then log " nada que proponer" sin_nada=$((sin_nada+1)) continue fi if [ -z "$cambios" ]; then log " aplicado fuera del fork (voz o ajustes), sin commit" aceptados=$((aceptados+1)) continue fi # ── LA PUERTA ── if timeout 2400 "$RAIZ/config/puerta.sh" completa > "$RAIZ/.puerta.txt" 2>&1; then if [ "$SECO" = si ]; then log " [seco] habria pasado la puerta; se revierte igual" (cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1) else (cd "$RAIZ/newelle" && git add -A && \ git commit -q -m "auto($mejora): mejora nocturna Propuesto y validado por config/noche.sh sin intervencion. Paso la puerta completa: catalogo valido, generador reproducible, emparejador sin perdidas, 150 acciones respondiendo y la suite en verde. $(echo "$salida" | head -3)") log " ACEPTADO y commiteado" aceptados=$((aceptados+1)) fi else log " RECHAZADO: $(tail -1 "$RAIZ/.puerta.txt")" (cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1) rechazados=$((rechazados+1)) fi done # Si una vuelta entera no propuso nada, no tiene sentido seguir dando # vueltas en vacio: se espera a que el uso genere datos nuevos. if [ "$sin_nada" -ge $(( ${#MEJORAS[@]} )) ]; then log "una vuelta entera sin nada que hacer; esperando 30 min" for _ in $(seq 180); do [ -f "$PARADA" ] && break; sleep 10; done sin_nada=0 fi done log "=== fin: $aceptados aceptados, $rechazados rechazados, $vuelta vueltas ===" cd "$RAIZ/newelle" log "commits de esta noche:" git log --oneline "$ANCLA"..HEAD 2>/dev/null | sed 's/^/ /' | tee -a "$DIARIO"