#!/usr/bin/env python3 """ Script para agregar YAML front-matter a archivos markdown que no lo tienen. Soporta múltiples proyectos del workspace. """ import os import re from datetime import datetime from pathlib import Path def extract_title_from_content(content: str, filename: str) -> str: """Extrae título del primer H1 o usa el nombre del archivo.""" match = re.search(r'^#\s+(.+)$', content, re.MULTILINE) if match: return match.group(1).strip() # Fallback: usar nombre de archivo name = Path(filename).stem return name.replace('-', ' ').replace('_', ' ').title() def determine_file_type(filepath: str, filename: str) -> str: """Determina el tipo de documento basado en path y nombre.""" filepath_lower = filepath.lower() filename_lower = filename.lower() # Por prefijo de archivo if filename_lower.startswith('rf-'): return 'Requirement' elif filename_lower.startswith('us-'): return 'User Story' elif filename_lower.startswith('et-'): return 'Technical Specification' elif filename_lower.startswith('adr-'): return 'ADR' elif filename_lower.startswith('epic-'): return 'Epic' elif filename_lower.startswith('pmc-') or filename_lower.startswith('oqi-'): return 'Module Definition' # Por nombre específico if '_map' in filename_lower or '_index' in filename_lower: return 'Index' elif 'readme' in filename_lower: return 'README' elif 'vision' in filename_lower: return 'Vision' elif 'arquitectura' in filename_lower or 'architecture' in filename_lower: return 'Architecture' elif 'roadmap' in filename_lower: return 'Roadmap' elif 'guia' in filename_lower or 'guide' in filename_lower: return 'Guide' elif 'glosario' in filename_lower: return 'Glossary' elif 'board' in filename_lower: return 'Planning' elif 'definition-of' in filename_lower: return 'Process' elif 'auditoria' in filename_lower: return 'Audit' elif 'analisis' in filename_lower: return 'Analysis' elif 'modelo' in filename_lower or 'esquema' in filename_lower: return 'Model' elif 'api' in filename_lower: return 'API Documentation' # Por carpeta if '/requerimientos/' in filepath_lower or '/requirements/' in filepath_lower: return 'Requirement' elif '/historias-usuario/' in filepath_lower or '/user-stories/' in filepath_lower: return 'User Story' elif '/especificaciones/' in filepath_lower: return 'Technical Specification' elif '/adr/' in filepath_lower or '97-adr' in filepath_lower: return 'ADR' elif '/planning/' in filepath_lower: return 'Planning' elif '/guias/' in filepath_lower or '/guides/' in filepath_lower: return 'Guide' return 'Documentation' def extract_epic_from_path(filepath: str) -> str: """Extrae el epic/módulo del path si existe.""" # Buscar patrones como OQI-001, PMC-001, EPIC-001, etc. match = re.search(r'(OQI-\d+|PMC-\d+|EPIC-\d+|BA-\d+|IA-\d+)', filepath, re.IGNORECASE) if match: return match.group(1).upper() return '' def generate_id(filename: str) -> str: """Genera un ID único basado en el nombre del archivo.""" name = Path(filename).stem return name.upper().replace(' ', '-') def determine_status(doc_type: str) -> str: """Determina el status por defecto según el tipo.""" if doc_type in ['Requirement', 'User Story', 'Technical Specification']: return 'Draft' elif doc_type in ['Vision', 'Architecture', 'Guide']: return 'Active' return 'Draft' def has_yaml_frontmatter(content: str) -> bool: """Verifica si el contenido ya tiene YAML front-matter.""" return content.strip().startswith('---') def add_yaml_frontmatter(filepath: str, project_name: str) -> bool: """Agrega YAML front-matter a un archivo si no lo tiene.""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() if has_yaml_frontmatter(content): return False filename = os.path.basename(filepath) title = extract_title_from_content(content, filename) doc_type = determine_file_type(filepath, filename) doc_id = generate_id(filename) epic = extract_epic_from_path(filepath) status = determine_status(doc_type) today = datetime.now().strftime('%Y-%m-%d') # Construir YAML yaml_lines = [ '---', f'id: "{doc_id}"', f'title: "{title}"', f'type: "{doc_type}"', ] if epic: yaml_lines.append(f'epic: "{epic}"') yaml_lines.extend([ f'status: "{status}"', f'project: "{project_name}"', f'version: "1.0.0"', f'created_date: "{today}"', f'updated_date: "{today}"', '---', '' ]) yaml_header = '\n'.join(yaml_lines) new_content = yaml_header + content with open(filepath, 'w', encoding='utf-8') as f: f.write(new_content) return True except Exception as e: print(f"Error procesando {filepath}: {e}") return False def process_project(docs_path: str, project_name: str): """Procesa todos los archivos .md de un proyecto.""" processed = 0 skipped = 0 errors = 0 for root, dirs, files in os.walk(docs_path): for filename in files: if filename.endswith('.md'): filepath = os.path.join(root, filename) try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() if has_yaml_frontmatter(content): skipped += 1 else: if add_yaml_frontmatter(filepath, project_name): processed += 1 print(f"✓ {filepath}") else: errors += 1 except Exception as e: errors += 1 print(f"✗ {filepath}: {e}") return processed, skipped, errors def main(): base_path = '/home/isem/workspace-v1/projects' projects = [ ('platform_marketing_content', 'platform_marketing_content'), ('trading-platform', 'trading-platform'), ] total_processed = 0 total_skipped = 0 total_errors = 0 for folder, project_name in projects: docs_path = os.path.join(base_path, folder, 'docs') if os.path.exists(docs_path): print(f"\n=== Procesando: {project_name} ===") processed, skipped, errors = process_project(docs_path, project_name) print(f" Processed: {processed}, Skipped: {skipped}, Errors: {errors}") total_processed += processed total_skipped += skipped total_errors += errors else: print(f"No existe: {docs_path}") print(f"\n=== TOTAL ===") print(f"Processed: {total_processed}") print(f"Skipped: {total_skipped}") print(f"Errors: {total_errors}") if __name__ == '__main__': main()