Refactor(ADN): Migración de tools/adn a adn/tools en busca de la Armonía Integral del DIIAA

This commit is contained in:
Ricardo Monla
2026-03-10 22:25:01 -03:00
parent 174ef7d53c
commit 78006032eb
69 changed files with 432 additions and 671 deletions
Executable
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# tools/adn.rb — Interfaz de línea de comandos unificada para el sistema ADN
# ============================================================================
# 🤖 DIRECTIVA IA: Esta herramienta es el punto de entrada principal para la
# automatización del ADN. Su objetivo es reducir progresivamente la dependencia
# de la IA mediante scripts Ruby autónomos.
# ============================================================================
# Uso: ./adn/tools/run <subcomando> [opciones] [argumentos]
#
# Subcomandos disponibles:
# validador [--watch] [--fix] Validar cumplimiento del ADN en bitácoras
# contexto <nodo> [--cmd] Obtener contexto topológico de un nodo
# salud [--dashboard] [--json] Ver salud general del sistema ADN
# generar bitacora [fecha] Generar nueva bitácora (fecha: YYYY-MM-DD)
# generar nodo <nombre> Generar ficha de nodo
# generar proyecto <código> Generar manifiesto de proyecto
# conocimiento asimilar <archivo> Asimilar documentación al ADN
# evolucion <propuesta.md> Gestionar propuesta de evolución del ADN
# ayuda [subcomando] Mostrar ayuda detallada
#
# Documentación completa: docs/plan/260307-1400_Plan_Mejoras_ADN.md
require 'optparse'
require 'json'
require 'fileutils'
require 'date'
require 'time'
module ADN
# El script ahora está en adn/tools/run, por lo que la raíz es 2 niveles arriba
PROJECT_ROOT = File.expand_path('../..', __dir__)
NODOS_DIR = File.join(PROJECT_ROOT, 'nodos')
PROYECTOS_DIR = File.join(PROJECT_ROOT, 'docs', 'proyectos')
# Corrección a las rutas de bitácoras y logs
BITACORAS_DIR = File.join(PROJECT_ROOT, 'docs', 'bitacoras')
LOGS_DIR = File.join(PROJECT_ROOT, 'docs', 'bitacoras', '_hist')
# Aseguramos que los directorios existan
FileUtils.mkdir_p(LOGS_DIR) unless Dir.exist?(LOGS_DIR)
FileUtils.mkdir_p(BITACORAS_DIR) unless Dir.exist?(BITACORAS_DIR)
end
require_relative 'core/logger'
require_relative 'core/eventos'
require_relative 'core/triggers'
require_relative 'core/validador'
require_relative 'cli/db'
require_relative 'cli/plan'
require_relative 'cli/backup'
require_relative 'cli/triggers'
require_relative 'cli/conocimiento'
require_relative 'cli/cierre'
require_relative 'cli/inicio'
require_relative 'core/conciliador'
require_relative 'core/colores'
# ─── Logger estructurado ─────────────────────────────────────────────────
# El logger está implementado en adn/logger.rb
ADNLogger = ADN::Logger
require_relative 'cli/validador'
require_relative 'cli/contexto'
require_relative 'cli/generar'
require_relative 'cli/salud'
require_relative 'cli/ayuda'
# ─── CLI Principal ──────────────────────────────────────────────────────
class ADNCLI
def initialize
@logger = ADNLogger.new
end
def ejecutar
if ARGV.empty?
SubcomandoAyuda.new([], @logger).ejecutar
exit 0
end
subcomando = ARGV[0]
# Saneamiento preventivo de pendientes (con cooldown de 5 minutos para performance)
ejecutar_saneamiento_preventivo(subcomando)
args = ARGV[1..-1] || []
case subcomando
when 'validador'
SubcomandoValidador.new(args, @logger).ejecutar
when 'contexto'
SubcomandoContexto.new(args, @logger).ejecutar
when 'salud'
SubcomandoSalud.new(args, @logger).ejecutar
when 'generar'
SubcomandoGenerar.new(args, @logger).ejecutar
when 'conocimiento'
ADN::SubcomandoConocimiento.new(args, @logger).ejecutar
when 'db'
ADN::SubcomandoDB.new(args, @logger).ejecutar
when 'backup', 'backups'
ADN::SubcomandoBackup.new(args, @logger).ejecutar
when 'triggers'
ADN::SubcomandoTriggers.new(args, @logger).ejecutar
when 'cierre'
ADN::SubcomandoCierre.new(args, @logger).ejecutar
when 'inicio'
ADN::SubcomandoInicio.new(args, @logger).ejecutar
when 'ayuda', 'help', '-h', '--help'
SubcomandoAyuda.new(args, @logger).ejecutar
when 'evolucion'
@logger.info("Subcomando 'evolucion' será implementado en Fase 2")
puts "#{Color::YELLOW}⚠ El subcomando 'evolucion' será implementado en Fase 2#{Color::RESET}"
puts " Ver plan: docs/plan/260307-1400_Plan_Mejoras_ADN.md"
when 'triggers'
ADN::SubcomandoTriggers.new(args, @logger).ejecutar
when 'plan'
ADN::SubcomandoPlan.new(args, @logger).ejecutar
else
@logger.error("Subcomando desconocido: #{subcomando}")
puts "#{Color::RED}✗ Subcomando desconocido: '#{subcomando}'#{Color::RESET}"
puts ""
SubcomandoAyuda.new([], @logger).ejecutar
exit 1
end
rescue Interrupt
puts "\n#{Color::YELLOW}⚠ Interrumpido por el usuario#{Color::RESET}"
@logger.info("Ejecución interrumpida por usuario")
exit 130
rescue => e
@logger.error("Error inesperado: #{e.message}", { backtrace: e.backtrace })
puts "#{Color::RED}✗ Error inesperado: #{e.message}#{Color::RESET}"
puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG']
exit 1
end
private
def ejecutar_saneamiento_preventivo(subcomando)
# No sanear si es ayuda o db (para evitar bucles o lentitud en info rápida)
return if ['ayuda', 'help', 'db', 'salud', '-h', '--help'].include?(subcomando)
archivo_cooldown = "/tmp/adn_sanity_cooldown"
cooldown_segundo = 300 # 5 minutos
if File.exist?(archivo_cooldown)
last_run = File.mtime(archivo_cooldown)
return if (Time.now - last_run) < cooldown_segundo
end
# Ejecutar conciliación
begin
# Tocamos el archivo primero para evitar que otras instancias concurrentes lo lancen
FileUtils.touch(archivo_cooldown)
ADN::Conciliador.new(@logger).ejecutar(silencioso: true)
rescue => e
@logger.advertencia("No se pudo completar el saneamiento preventivo: #{e.message}")
end
end
end
# ─── Punto de entrada ───────────────────────────────────────────────────
if __FILE__ == $0
ADNCLI.new.ejecutar
end