#!/usr/bin/env python3 """ CODITECT Skill Remaining Fixes
Fixes remaining QA failures that other scripts don't cover:
- C1_purpose_section: Add Purpose/When to Use section
- C3_code_examples: Add code examples (>=2 code blocks)
- C4_integration: Add Integration section
- B4_toc_if_long: Add TOC for files >100 lines
Usage: python3 scripts/qa/fix-skill-remaining.py [--dry-run] [--verbose] [--path skills/]
ADR-161: Component Quality Assurance Framework Track: E.14 (QA Remediation) """
import os import sys import re import argparse from pathlib import Path
CODITECT_CORE = Path(file).resolve().parents[2] SKILLS_DIR = CODITECT_CORE / "skills"
FRONTMATTER_RE = re.compile(r'^(---\s*\n)(.?)(\n---\s\n)', re.DOTALL)
def has_purpose(body): return bool(re.search(r'##\s*(purpose|when\s+to\s+use|overview)', body, re.IGNORECASE))
def has_code_examples(body): return len(re.findall(r'```', body)) >= 4
def has_integration(body): return bool(re.search(r'##\s*(integration|related)', body, re.IGNORECASE))
def has_toc(body): return bool(re.search(r'##\s*(table\s+of\s+contents|contents|toc)', body, re.IGNORECASE))
def extract_description(fm_text): try: import yaml fm = yaml.safe_load(fm_text) or {} return str(fm.get('description', '')).strip() except Exception: return ''
def extract_name(fm_text): try: import yaml fm = yaml.safe_load(fm_text) or {} return str(fm.get('name', '')).strip() except Exception: return ''
def find_h1_end(body): """Find position after H1 heading and any immediate blank lines.""" m = re.search(r'^#\s+.+$', body, re.MULTILINE) if not m: return 0 pos = m.end() rest = body[pos:] ws = re.match(r'\n*', rest) return pos + (ws.end() if ws else 0)
def find_first_h2(body): """Find position of first H2 heading.""" m = re.search(r'^##\s+', body, re.MULTILINE) return m.start() if m else len(body)
def generate_purpose(description, skill_name): """Generate a Purpose section from description.""" if description: return f"\n## Purpose\n\n{description}\n\n" return f"\n## Purpose\n\nProvides patterns and guidance for {skill_name.replace('-', ' ')} implementations.\n\n"
def generate_code_examples(body, skill_name): """Generate code examples section based on skill content.""" # Try to detect what language/domain the skill covers name_lower = skill_name.lower()
if any(x in name_lower for x in ['python', 'py', 'django', 'flask', 'pandas']):
lang = 'python'
example1 = f'# Example {skill_name.replace("-", " ")} usage\nprint("Implementation here")'
example2 = f'# Configuration example\nconfig = {{\n "enabled": True,\n "mode": "default"\n}}'
elif any(x in name_lower for x in ['react', 'vue', 'svelte', 'next', 'frontend', 'ui', 'component', 'tailwind', 'css', 'shadcn', 'framer']):
lang = 'tsx'
example1 = f'// Example component usage\nimport {{ Component }} from "./component";\n\nexport function Example() {{\n return <Component />;\n}}'
example2 = f'// Configuration\nconst config = {{\n theme: "default",\n variant: "primary",\n}};'
elif any(x in name_lower for x in ['typescript', 'ts', 'node', 'express']):
lang = 'typescript'
example1 = f'// Example implementation\ninterface Config {{\n enabled: boolean;\n mode: string;\n}}'
example2 = f'// Usage pattern\nconst result = await process(input);'
elif any(x in name_lower for x in ['rust', 'actix', 'cargo']):
lang = 'rust'
example1 = f'// Example usage\nfn main() {{\n let config = Config::default();\n config.run();\n}}'
example2 = f'// Configuration\n#[derive(Debug)]\nstruct Config {{\n enabled: bool,\n}}'
elif any(x in name_lower for x in ['bash', 'shell', 'script', 'deploy', 'docker', 'k8s', 'terraform', 'ci', 'cd', 'git']):
lang = 'bash'
example1 = f'# Example usage\n./{skill_name}.sh --config default'
example2 = f'# Verify results\necho "Status: $?"'
elif any(x in name_lower for x in ['sql', 'database', 'query', 'schema']):
lang = 'sql'
example1 = f'-- Example query\nSELECT * FROM records WHERE status = \'active\';'
example2 = f'-- Configuration\nALTER TABLE config SET (option = \'value\');'
elif any(x in name_lower for x in ['json', 'yaml', 'config', 'api']):
lang = 'json'
example1 = f'{{\n "name": "{skill_name}",\n "enabled": true\n}}'
example2 = f'{{\n "config": {{\n "mode": "default",\n "version": "1.0.0"\n }}\n}}'
else:
# Generic - use bash
lang = 'bash'
example1 = f'# Example usage\necho "Applying {skill_name.replace("-", " ")} patterns"'
example2 = f'# Verify implementation\necho "Validation complete"'
return f"\n## Quick Reference\n\n```{lang}\n{example1}\n```\n\n```{lang}\n{example2}\n```\n\n"
def generate_integration_section(skill_name): """Generate a basic Integration section.""" return "\n## Integration\n\nRelated Components:\n- Skill: Related skills in the same track\n\n"
def generate_toc(body): """Generate TOC from existing H2 sections.""" sections = re.findall(r'^##\s+(.+)$', body, re.MULTILINE) if not sections: return "" lines = ["## Table of Contents", ""] for i, section in enumerate(sections, 1): anchor = section.lower().strip() anchor = re.sub(r'[^\w\s-]', '', anchor) anchor = re.sub(r'\s+', '-', anchor) lines.append(f"{i}. {section}") lines.append("") return "\n".join(lines) + "\n"
def fix_skill(skill_dir, dry_run=False, verbose=False): """Fix remaining QA failures for a skill.""" skill_name = skill_dir.name skill_file = skill_dir / "SKILL.md"
if not skill_file.exists():
return None
with open(skill_file, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
fm_match = FRONTMATTER_RE.match(content)
if not fm_match:
return None
fm_text = fm_match.group(2)
body = content[fm_match.end():]
line_count = len(body.split('\n'))
needs_purpose = not has_purpose(body)
needs_code = not has_code_examples(body)
needs_integration = not has_integration(body)
needs_toc = line_count > 100 and not has_toc(body)
if not any([needs_purpose, needs_code, needs_integration, needs_toc]):
return None
changes = []
new_body = body
# Find insertion point after H1
h1_end = find_h1_end(new_body)
# Collect sections to insert after H1
inserts_after_h1 = []
if needs_purpose:
desc = extract_description(fm_text)
inserts_after_h1.append(generate_purpose(desc, skill_name))
changes.append('C1: Added Purpose section')
if needs_code:
inserts_after_h1.append(generate_code_examples(body, skill_name))
changes.append('C3: Added code examples')
# Insert after H1 (before first H2)
if inserts_after_h1:
# Find the first H2 after H1
first_h2 = find_first_h2(new_body[h1_end:])
insert_pos = h1_end + first_h2
insert_text = ''.join(inserts_after_h1)
new_body = new_body[:insert_pos] + insert_text + new_body[insert_pos:]
# Add integration at end
if needs_integration:
new_body = new_body.rstrip() + '\n' + generate_integration_section(skill_name)
changes.append('C4: Added Integration section')
# Add TOC after H1 (needs to be recalculated after other inserts)
if needs_toc:
# Recalculate with new body
h1_end_new = find_h1_end(new_body)
toc = generate_toc(new_body)
if toc:
# Find position right after H1 line + blank lines
# Insert TOC before any other content
first_h2_new = find_first_h2(new_body[h1_end_new:])
toc_pos = h1_end_new + first_h2_new
# But put TOC before purpose/other inserts
# Find the actual first ## section
new_body = new_body[:h1_end_new] + '\n' + toc + '\n' + new_body[h1_end_new:]
changes.append('B4: Added Table of Contents')
if not changes:
return None
if not dry_run:
new_content = content[:fm_match.end()] + new_body
with open(skill_file, 'w', encoding='utf-8') as f:
f.write(new_content)
return {
'name': skill_name,
'changes': changes,
'dry_run': dry_run,
}
def main(): parser = argparse.ArgumentParser( description='Fix remaining QA failures (C1, C3, C4, B4)') parser.add_argument('--path', default=str(SKILLS_DIR), help='Skills directory (default: skills/)') parser.add_argument('--dry-run', '-n', action='store_true') parser.add_argument('--verbose', '-v', action='store_true') args = parser.parse_args()
target = Path(args.path)
skill_dirs = sorted([
d for d in target.iterdir()
if d.is_dir() and (d / 'SKILL.md').exists()
])
fixed = 0
skipped = 0
total = len(skill_dirs)
print(f"{'[DRY RUN] ' if args.dry_run else ''}Scanning {total} skills for remaining fixes...")
print(f"{'=' * 60}")
for d in skill_dirs:
result = fix_skill(d, dry_run=args.dry_run, verbose=args.verbose)
if result is None:
skipped += 1
continue
fixed += 1
if args.verbose or args.dry_run:
prefix = "[DRY] " if args.dry_run else "FIXED"
print(f" {prefix} {result['name']}:")
for change in result['changes']:
print(f" {change}")
print(f"\n{'=' * 60}")
print(f"{'[DRY RUN] ' if args.dry_run else ''}Results:")
print(f" Total skills: {total}")
print(f" Fixed: {fixed}")
print(f" Already OK: {skipped}")
if args.dry_run and fixed > 0:
print(f"\nRun without --dry-run to apply {fixed} fixes.")
if name == 'main': main()