Skip to main content

scripts-repair-skill-frontmatter

#!/usr/bin/env python3 """ Repair corrupted skill frontmatter from batch CEF update.

The issue: CEF fields were inserted after 'tags:' but before the tag list items, causing malformed YAML.

Usage: python3 scripts/repair-skill-frontmatter.py [--dry-run] """

import argparse import re import sys from pathlib import Path

def parse_frontmatter(content: str) -> tuple: """Extract frontmatter and body from SKILL.md.""" match = re.match(r'^(---\s*\n)(.?)(\n---\s\n)', content, re.DOTALL) if not match: return None, None, content

return match.group(1), match.group(2), content[match.end():]

def repair_frontmatter(frontmatter: str) -> str: """Repair malformed frontmatter.""" lines = frontmatter.split('\n')

# Find cef fields and any loose list items
cef_lines = []
tag_items = []
other_lines = []
in_tags = False
i = 0

while i < len(lines):
line = lines[i]
stripped = line.strip()

# Check if this is a cef field
if stripped.startswith('cef_'):
cef_lines.append(line)
i += 1
continue

# Check if this is a tag item (starts with - ) that appears after cef fields
if stripped.startswith('- ') and cef_lines:
# This is a tag item that got misplaced
tag_items.append(line)
i += 1
continue

# Check for tags: header
if stripped == 'tags:':
other_lines.append(line)
in_tags = True
i += 1
# Collect all immediate tag items after tags:
while i < len(lines):
next_line = lines[i]
if next_line.strip().startswith('- '):
tag_items.append(next_line)
i += 1
elif next_line.strip() == '':
# Empty line - could be end of tags or just spacing
# Check if next non-empty line is cef_
j = i + 1
while j < len(lines) and lines[j].strip() == '':
j += 1
if j < len(lines) and lines[j].strip().startswith('cef_'):
# Empty line before cef, skip it
i += 1
break
else:
other_lines.append(next_line)
i += 1
else:
break
continue

# Check for end of tags (new field that's not a list item)
if in_tags and stripped and not stripped.startswith('- ') and not stripped.startswith('#'):
in_tags = False

other_lines.append(line)
i += 1

# Rebuild frontmatter with correct order
result = []

# Add non-tag, non-cef lines first
for line in other_lines:
result.append(line)
if line.strip() == 'tags:':
# Add all tag items right after tags:
for tag in tag_items:
result.append(tag)

# Add empty line if needed before cef fields
if result and result[-1].strip() != '':
result.append('')

# Add cef fields at the end
for line in cef_lines:
result.append(line)

return '\n'.join(result)

def repair_skill_file(skill_path: Path, dry_run: bool = False) -> bool: """Repair a single skill file.""" content = skill_path.read_text()

# Check if file has cef_track
if 'cef_track:' not in content:
return False

# Check if already repaired (tags should come before cef_)
# Find position of tags: and cef_track:
tags_pos = content.find('\ntags:')
cef_pos = content.find('\ncef_track:')

if tags_pos == -1 or cef_pos == -1:
return False

# Extract content between tags: and cef_track:
between = content[tags_pos:cef_pos]

# If there are list items AFTER cef fields (outside frontmatter), it's corrupted
# Check if there's a list item pattern after cef fields but before ---
frontmatter_end = content.find('\n---', cef_pos)
if frontmatter_end == -1:
return False

section_after_cef = content[cef_pos:frontmatter_end]

# If we see " - " pattern after cef fields in frontmatter, it's corrupted
if re.search(r'\n - \w', section_after_cef):
# Need repair
pass
else:
# Already OK
return False

# Parse and repair
separator, frontmatter, body = parse_frontmatter(content)
if frontmatter is None:
return False

repaired_fm = repair_frontmatter(frontmatter)

new_content = separator + repaired_fm + '\n---\n' + body

if dry_run:
print(f"Would repair: {skill_path}")
return True

skill_path.write_text(new_content)
print(f"✅ Repaired: {skill_path}")
return True

def main(): parser = argparse.ArgumentParser(description="Repair corrupted skill frontmatter") parser.add_argument("--dry-run", action="store_true", help="Preview changes without applying") args = parser.parse_args()

skills_dir = Path("skills")
skill_files = list(skills_dir.glob("*/SKILL.md"))

print(f"Checking {len(skill_files)} skill files...")
print(f"Mode: {'DRY RUN' if args.dry_run else 'LIVE'}")
print()

repaired = 0
checked = 0

for skill_path in skill_files:
checked += 1
if repair_skill_file(skill_path, args.dry_run):
repaired += 1

print()
print(f"Checked: {checked} files")
print(f"Repaired: {repaired} files")

if args.dry_run:
print("\nRun without --dry-run to apply repairs")

return 0

if name == "main": sys.exit(main())