#!/usr/bin/env python3 """Fix markdown linting errors: MD040, MD013, MD060, MD034.""" import re import sys from pathlib import Path def fix_code_block_languages(content): """Add language specifier to code blocks without one (MD040).""" # Pattern: opening fence with no language specifier (``` followed by newline) pattern = r'(^```)\n' fixed = re.sub(pattern, r'\1text\n', content, flags=re.MULTILINE) return fixed def fix_line_length(content): """Break long lines to fit 150 character limit (MD013).""" lines = content.split('\n') fixed_lines = [] for line in lines: # Skip code blocks, tables, headings with formatting if line.startswith('```') or line.startswith('|') or line.startswith('>'): fixed_lines.append(line) continue # If line is longer than 150 chars if len(line) > 150: # For paragraphs, break at word boundaries if not line.startswith('#') and not line.startswith('-') and not line.startswith('*'): words = line.split(' ') current_line = '' for word in words: test_line = current_line + (' ' if current_line else '') + word if len(test_line) <= 150: current_line = test_line else: if current_line: fixed_lines.append(current_line) current_line = word if current_line: fixed_lines.append(current_line) continue fixed_lines.append(line) return '\n'.join(fixed_lines) def fix_table_formatting(content): """Fix table formatting: ensure spaces around all pipes (MD060).""" lines = content.split('\n') fixed_lines = [] i = 0 while i < len(lines): line = lines[i] # Check if this is a table line (contains pipes) if '|' in line: # For markdown tables, we need to ensure: # 1. Each cell has space after opening | and before closing | # 2. Header separator is properly formatted # Split by pipes cells = line.split('|') # Rebuild with proper spacing # First element might be empty (if line starts with |) fixed_cells = [] for j, cell in enumerate(cells): # For separator rows (containing dashes), just trim and preserve if '-' in cell and not any(c.isalnum() for c in cell.replace('-', '').replace(' ', '')): # This is a separator cell - ensure it's proper format: --- trimmed = cell.strip() if trimmed and all(c == '-' or c == ':' for c in trimmed): fixed_cells.append(' ' + trimmed + ' ') else: fixed_cells.append(' ' + cell.strip() + ' ') else: # Regular cell - ensure spaces trimmed = cell.strip() if trimmed: fixed_cells.append(' ' + trimmed + ' ') else: fixed_cells.append(' ') fixed_line = '|' + '|'.join(fixed_cells) + '|' fixed_lines.append(fixed_line) else: fixed_lines.append(line) i += 1 return '\n'.join(fixed_lines) def fix_bare_urls(content): """Convert bare URLs to markdown links (MD034).""" # Pattern: URL not already in markdown link or code block # This is conservative - only fixes obvious cases pattern = r'([^[])(https?://[^\s\)]+)' fixed = re.sub(pattern, r'\1[\2](\2)', content) return fixed def fix_file(filepath): """Fix all markdown linting errors in a single file.""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # Apply fixes in order content = fix_code_block_languages(content) content = fix_line_length(content) content = fix_table_formatting(content) content = fix_bare_urls(content) with open(filepath, 'w', encoding='utf-8') as f: f.write(content) return True except Exception as e: print(f"Error processing {filepath}: {e}", file=sys.stderr) return False def main(): """Fix markdown linting errors in all AI documentation files.""" docs_root = Path('provisioning/docs/src') # All AI files (complete list) ai_files = [ 'ai/ai-agents.md', 'ai/ai-assisted-forms.md', 'ai/architecture.md', 'ai/config-generation.md', 'ai/configuration.md', 'ai/cost-management.md', 'ai/mcp-integration.md', 'ai/natural-language-config.md', 'ai/rag-system.md', 'ai/README.md', 'ai/security-policies.md', 'ai/troubleshooting-with-ai.md', ] success_count = 0 for filepath_rel in ai_files: filepath = docs_root / filepath_rel if filepath.exists(): if fix_file(filepath): print(f"āœ“ Fixed {filepath_rel}") success_count += 1 else: print(f"āœ— Failed to fix {filepath_rel}") else: print(f"⚠ File not found: {filepath_rel}") print(f"\nāœ“ Fixed {success_count}/{len(ai_files)} AI files") return 0 if success_count == len(ai_files) else 1 if __name__ == '__main__': sys.exit(main())