# frozen_string_literal: true # tools/adn/cli/conocimiento.rb — Subcomando para gestionar la Base de Conocimiento del ADN # ============================================================================== # Inyecta nuevos documentos y los referencia automáticamente en el 00_indice.md # cumpliendo con el principio de Armonía Integral. require 'optparse' require 'fileutils' require 'date' module ADN class SubcomandoConocimiento def initialize(args, logger) @args = args @logger = logger @indice_path = File.join(PROJECT_ROOT, 'adn', '00_indice.md') @conocimiento_dir = File.join(PROJECT_ROOT, 'adn', 'conocimiento') end def ejecutar if @args.empty? || @args[0] == 'help' mostrar_ayuda return end subcomando = @args[0] sub_args = @args[1..-1] || [] case subcomando when 'asimilar' comando_asimilar(sub_args) else @logger.error("Subcomando desconocido: conocimiento #{subcomando}") puts "#{Color::RED}✗ Subcomando desconocido: '#{subcomando}'#{Color::RESET}" mostrar_ayuda exit 1 end rescue Interrupt puts "\n#{Color::YELLOW}⚠ Operación interrumpida#{Color::RESET}" exit 130 rescue StandardError => e @logger.error("Error asimilando conocimiento: #{e.message}") puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" puts "#{Color::DIM}#{e.backtrace.first}#{Color::RESET}" if ENV['ADN_DEBUG'] exit 1 end private def comando_asimilar(args) opts = { forzar: false } parser = OptionParser.new do |opt| opt.banner = "Uso: adn conocimiento asimilar [OPCIONES]" opt.on("-f", "--force", "Forzar sobrescritura si el documento ya está asimilado") do opts[:forzar] = true end opt.on("-h", "--help", "Mostrar ayuda") do puts opt exit end end begin parser.parse!(args) rescue OptionParser::InvalidOption => e puts "#{Color::RED}✗ Error: #{e.message}#{Color::RESET}" puts parser exit 1 end archivo_origen = args[0] if archivo_origen.nil? || archivo_origen.strip.empty? puts "#{Color::RED}✗ Se requiere la ruta de un archivo Markdown.#{Color::RESET}" puts parser exit 1 end unless File.exist?(archivo_origen) puts "#{Color::RED}✗ El archivo origen no existe: #{archivo_origen}#{Color::RESET}" exit 1 end puts "#{Color::CYAN}🧠 Asimilando conocimiento desde #{archivo_origen}...#{Color::RESET}" # Preparar el directorio de conocimiento FileUtils.mkdir_p(@conocimiento_dir) unless Dir.exist?(@conocimiento_dir) nombre_base = File.basename(archivo_origen) archivo_destino = File.join(@conocimiento_dir, nombre_base) if File.exist?(archivo_destino) && !opts[:forzar] puts "#{Color::YELLOW}⚠ El documento ya existe en la base de ADN. Usa --force para sobrescribir.#{Color::RESET}" exit 1 end # Copiar el archivo FileUtils.cp(archivo_origen, archivo_destino) puts " #{Color::DIM}Documento integrado físicamente en: adn/conocimiento/#{nombre_base}#{Color::RESET}" # Extraer metadatos contenido = File.read(archivo_destino) titulo = contenido.match(/^#\s+(.+)$/)&.captures&.first || nombre_base.sub('.md', '').capitalize # Buscar el primer párrafo que no sea un título ni un bloque especial resumen = "Asimilación automatizada de módulo." parrafos = contenido.split("\n\n").reject { |p| p.start_with?('#', '>', '<', '---', '!') || p.strip.empty? } if parrafos.any? resumen_limpio = parrafos.first.gsub("\n", " ").strip resumen = resumen_limpio[0..147] + "..." if resumen_limpio.length > 150 resumen = resumen_limpio if resumen_limpio.length <= 150 end # Actualizar 00_indice.md actualizar_indice(nombre_base, titulo, resumen) puts "#{Color::GREEN}✅ Conocimiento asimilado correctamente y propagado a ADN/00_indice.md#{Color::RESET}" @logger.info("Conocimiento asimilado: #{nombre_base}") end def actualizar_indice(nombre_archivo, titulo, resumen) contenido_indice = File.read(@indice_path) fecha_hoy = Date.today.strftime("%d/%m/%Y") ruta_relativa = "conocimiento/#{nombre_archivo}" fila_markdown = "| [**#{titulo}**](#{ruta_relativa}) | #{fecha_hoy} | #{resumen} |" # Comprobar si ya existe la sección de conocimiento seccion_conocimiento = "## 📚 Base de Conocimiento" if contenido_indice.include?(seccion_conocimiento) # Revisión de duplicados return if contenido_indice.include?(ruta_relativa) && !contenido_indice.include?("Sobrescribir") # Encontrar el final de la tabla e insertar la fila (reemplazando si hace falta actualizar) lineas = contenido_indice.lines idx_seccion = lineas.index { |l| l.include?(seccion_conocimiento) } # Insertar después de las cabeceras de la tabla idx_insercion = idx_seccion + 4 lineas.insert(idx_insercion, "#{fila_markdown}\n") File.write(@indice_path, lineas.join) else # Inyectar toda la sección al final del archivo bloque_nuevo = <<~MD --- #{seccion_conocimiento} | Documento | Fecha de Asimilación | Descripción | | :--- | :--- | :--- | #{fila_markdown} MD File.open(@indice_path, 'a') do |f| f.puts bloque_nuevo end end end def mostrar_ayuda puts "#{Color::CYAN}🧠 Herramienta ADN - Gestión de Base de Conocimiento#{Color::RESET}" puts "Permite inyectar, organizar e indexar nueva documentación en el motor autónomo." puts "" puts "#{Color::BOLD}SUBCOMANDOS:#{Color::RESET}" puts " #{Color::CYAN}asimilar #{Color::RESET} Ingiere un documento Markdown a la base cognitiva del ADN." puts "" puts "#{Color::BOLD}EJEMPLOS:#{Color::RESET}" puts " ./tools/adn/run conocimiento asimilar docs/configuracion_bd.md" end end end