Skip to main content

scripts-fix-all-markdown-errors

#!/usr/bin/env python3 """

CODITECT Markdown Quality System - Master Orchestrator Copyright © 2025 AZ1.AI INC - All Rights Reserved

This software is proprietary and confidential. Unauthorized copying, distribution, or use is strictly prohibited.

CODITECT owns all intellectual property rights to this implementation. """

""" Master Markdown Fix Orchestrator

Runs all markdown fixes in the correct order to ensure 100% error resolution. Uses multi-pass approach for interdependent rules.

Usage: python3 scripts/fix-all-markdown-errors.py # Fix all .md files python3 scripts/fix-all-markdown-errors.py --dry-run # Preview changes python3 scripts/fix-all-markdown-errors.py path/to/file.md # Fix specific file python3 scripts/fix-all-markdown-errors.py --verify-only # Check current status """

import subprocess import sys from pathlib import Path import argparse import json from datetime import datetime

Get script directory for path resolution (works from any cwd)

SCRIPT_DIR = Path(file).resolve().parent CORE_ROOT = SCRIPT_DIR.parent

Execution order matters! Some fixes depend on others.

FIX_ORDER = [ # Phase 1: Basic cleanup (must run first) ('MD009', 'Trailing spaces', 'fix-md009-trailing-spaces.py'), ('MD027', 'Multiple spaces after blockquote', 'fix-md027.py'), ('MD037', 'Spaces inside emphasis', 'fix-md037.py'),

# Phase 2: Structural fixes
('MD018', 'Space after hash', 'fix-md018-no-space-after-hash.py'),
('MD023', 'Headings at start', 'fix-md023-headings-at-start-of-line.py'),
('MD026', 'Trailing punctuation in headings', 'fix-md026.py'),
('MD001', 'Heading level increments', 'fix-md001.py'),
('MD003', 'Heading style', 'fix-md003.py'),

# Phase 3: Code blocks and lists
('MD007', 'List indentation', 'fix-md007.py'),
('MD031', 'Code block blank lines', 'fix-md031-fenced-code-blank-lines.py'),
('MD032', 'List blank lines', 'fix-md032-lists-blank-lines.py'),

# Phase 4: Headings spacing (after other heading fixes)
('MD022', 'Headings blank lines', 'fix-md022-headings-blank-lines.py'),

# Phase 5: Links and media
('MD034', 'Bare URLs', 'fix-md034.py'),
('MD042', 'Empty links', 'fix-md042.py'),
('MD045', 'Image alt text', 'fix-md045.py'),

# Phase 6: Tables
('MD058', 'Table blank lines', 'fix-md058.py'),
('MD060', 'Table headers', 'fix-md060-table-formatting.py'),

]

class MarkdownFixer: def init(self, dry_run=False, verbose=False, verify_only=False): self.dry_run = dry_run self.verbose = verbose self.verify_only = verify_only self.results = [] self.scripts_dir = SCRIPT_DIR # Use script's own directory

def count_errors_before_after(self):
"""Count markdown errors using markdownlint."""
try:
result = subprocess.run(
['markdownlint-cli2', '**/*.md'],
capture_output=True,
text=True,
cwd='.'
)
# Count errors by type
errors = {}
for line in result.stdout.split('\n') + result.stderr.split('\n'):
if 'MD' in line:
for rule_code, _, _ in FIX_ORDER:
if rule_code in line:
errors[rule_code] = errors.get(rule_code, 0) + 1
return errors
except Exception as e:
print(f"Warning: Could not run markdownlint: {e}")
return {}

def run_fix_script(self, rule_code, description, script_name):
"""Run a single fix script."""
script_path = self.scripts_dir / script_name

if not script_path.exists():
return {
'rule': rule_code,
'description': description,
'status': 'SKIP',
'reason': f'Script not found: {script_name}',
'fixes': 0
}

try:
args = ['python3', str(script_path)]
if self.dry_run:
args.append('--dry-run')

result = subprocess.run(
args,
capture_output=True,
text=True,
timeout=300 # 5 minute timeout per script
)

# Parse output for fix count
fixes = 0
for line in result.stdout.split('\n'):
if 'Total:' in line:
try:
fixes = int(line.split(':')[1].split()[0])
except:
pass

status = 'SUCCESS' if result.returncode == 0 else 'ERROR'

return {
'rule': rule_code,
'description': description,
'status': status,
'fixes': fixes,
'output': result.stdout if self.verbose else None,
'error': result.stderr if result.returncode != 0 else None
}

except subprocess.TimeoutExpired:
return {
'rule': rule_code,
'description': description,
'status': 'TIMEOUT',
'fixes': 0
}
except Exception as e:
return {
'rule': rule_code,
'description': description,
'status': 'ERROR',
'fixes': 0,
'error': str(e)
}

def run_all_fixes(self):
"""Run all fix scripts in order."""
print("=" * 80)
print("Markdown Error Fix Orchestrator")
print("=" * 80)
print()

if self.verify_only:
print("VERIFICATION MODE - Counting errors only\n")
errors_before = self.count_errors_before_after()
total_errors = sum(errors_before.values())

print(f"Current Error Count: {total_errors} errors across {len(errors_before)} rule types\n")

for rule_code, count in sorted(errors_before.items(), key=lambda x: x[1], reverse=True):
desc = next((d for r, d, _ in FIX_ORDER if r == rule_code), 'Unknown')
print(f" {rule_code}: {count:5d} - {desc}")

return

if self.dry_run:
print("DRY RUN MODE - No files will be modified\n")

print("Counting errors before fixes...\n")
errors_before = self.count_errors_before_after()
total_before = sum(errors_before.values())
print(f"Found {total_before} total errors\n")

print(f"Running {len(FIX_ORDER)} fix scripts in order...\n")
print("-" * 80)

for rule_code, description, script_name in FIX_ORDER:
print(f"\n[{rule_code}] {description}")
print(f" Running: {script_name}")

result = self.run_fix_script(rule_code, description, script_name)
self.results.append(result)

if result['status'] == 'SUCCESS':
if result['fixes'] > 0:
print(f" ✓ Fixed {result['fixes']} issues")
else:
print(f" ✓ No issues found")
elif result['status'] == 'SKIP':
print(f" ⊘ Skipped: {result.get('reason', 'Unknown')}")
else:
print(f" ✗ {result['status']}: {result.get('error', 'Unknown error')}")

print("\n" + "-" * 80)
print("\nCounting errors after fixes...\n")
errors_after = self.count_errors_before_after()
total_after = sum(errors_after.values())

self.print_summary(total_before, total_after, errors_before, errors_after)

def print_summary(self, total_before, total_after, errors_before, errors_after):
"""Print execution summary."""
print("\n" + "=" * 80)
print("SUMMARY")
print("=" * 80)

successful = sum(1 for r in self.results if r['status'] == 'SUCCESS')
skipped = sum(1 for r in self.results if r['status'] == 'SKIP')
failed = sum(1 for r in self.results if r['status'] in ['ERROR', 'TIMEOUT'])
total_fixes = sum(r['fixes'] for r in self.results)

print(f"\nScripts Executed: {len(self.results)}")
print(f" ✓ Successful: {successful}")
print(f" ⊘ Skipped: {skipped}")
print(f" ✗ Failed: {failed}")
print(f"\nTotal Fixes Applied: {total_fixes}")

print(f"\nError Count:")
print(f" Before: {total_before} errors")
print(f" After: {total_after} errors")

if total_before > 0:
improvement = ((total_before - total_after) / total_before) * 100
print(f" Improvement: {improvement:.1f}%")

if total_after == 0:
print("\n🎉 SUCCESS: All markdown errors fixed!")
elif total_after < total_before:
print(f"\n✓ Progress made: {total_before - total_after} errors resolved")
print(f" {total_after} errors remaining")
else:
print(f"\n⚠ Warning: Error count did not decrease")

# Save detailed results
if not self.dry_run:
results_file = SCRIPT_DIR / 'markdown-fix-results.json'
with open(results_file, 'w') as f:
json.dump({
'timestamp': datetime.now().isoformat(),
'total_before': total_before,
'total_after': total_after,
'scripts_run': len(self.results),
'total_fixes': total_fixes,
'results': self.results
}, f, indent=2)
print(f"\nDetailed results saved to: {results_file}")

def main(): parser = argparse.ArgumentParser( description='Fix all markdown errors in repository', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=doc ) parser.add_argument('paths', nargs='*', help='Specific paths to fix (default: all)') parser.add_argument('--dry-run', action='store_true', help='Preview changes without modifying files') parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed output') parser.add_argument('--verify-only', action='store_true', help='Only count errors, don't fix')

args = parser.parse_args()

fixer = MarkdownFixer(
dry_run=args.dry_run,
verbose=args.verbose,
verify_only=args.verify_only
)

fixer.run_all_fixes()

if name == 'main': main()