Skip to main content

#!/usr/bin/env python3 """ CODITECT Command QA Grader

Grades commands against CODITECT-STANDARD-COMMANDS.md criteria.

Weights: File Format (15%), YAML Frontmatter (35%), Specification Quality (30%), Documentation (20%)

Usage: python3 scripts/qa/grade-commands.py [path] [--json output.json] [--verbose]

ADR-161: Component Quality Assurance Framework """

import os import sys import re import json import argparse from pathlib import Path

sys.path.insert(0, os.path.dirname(file)) from qa_common import ( parse_frontmatter, count_words, grade_from_score, compute_weighted_score, aggregate_results, output_results, parse_tools_field, validate_tools )

CODITECT_CORE = Path(file).resolve().parents[2] COMMANDS_DIR = CODITECT_CORE / "commands"

def grade_command(filepath): """Grade a single command file.""" filename = os.path.basename(filepath) cmd_name = filename.replace('.md', '')

with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()

fm, body = parse_frontmatter(content)
body_lower = body.lower()
word_count = count_words(body)
scores = {}

# A. FILE FORMAT (15%)
scores['A1_filename_format'] = 1 if re.match(r'^[a-z0-9]+(-[a-z0-9]+)*\.md$', filename) else 0
scores['A2_in_commands_dir'] = 1 # Already reading from commands/
fm_name = str(fm.get('name', ''))
scores['A3_name_matches_file'] = 1 if fm_name == cmd_name else 0

# B. YAML FRONTMATTER (35%)
scores['B1_name_present'] = 1 if fm.get('name') or fm.get('title') else 0
desc = str(fm.get('description', ''))
scores['B2_description_present'] = 1 if desc and len(desc.strip()) >= 10 else 0
scores['B3_argument_hint'] = 1 if fm.get('argument-hint') or fm.get('argument_hint') else 0
tools_list = parse_tools_field(fm.get('allowed-tools', fm.get('allowed_tools', '')))
scores['B4_allowed_tools'] = 1 if validate_tools(tools_list) else 0
scores['B5_component_type'] = 1 if fm.get('component_type') == 'command' else 0

# C. SPECIFICATION QUALITY (30%)
scores['C1_usage_section'] = 1 if re.search(r'##\s*usage', body_lower) else 0
scores['C2_arguments_table'] = 1 if re.search(r'\|\s*argument|\|\s*flag|\|\s*option|\|\s*name\s*\|.*\|\s*type', body_lower) else 0
has_code_example = bool(re.search(r'```[\w]*\n.*?/\w+', body, re.DOTALL))
scores['C3_usage_example'] = 1 if has_code_example or re.search(r'##\s*examples?', body_lower) else 0
scores['C4_workflow_steps'] = 1 if re.search(r'##\s*(system\s+prompt|workflow|execution|steps)', body_lower) else 0

# D. DOCUMENTATION (20%)
scores['D1_related_components'] = 1 if re.search(r'##\s*related\s+components', body_lower) or re.search(r'\*\*(agent|skill|script)\*\*:', body_lower) else 0
scores['D2_markdown_headings'] = 1 if re.search(r'^#\s+', body, re.MULTILINE) and re.search(r'^##\s+', body, re.MULTILINE) else 0
scores['D3_code_blocks'] = 1 if re.search(r'```\w+', body) else 0

categories = [
('A_file_format', 15, ['A1_filename_format', 'A2_in_commands_dir', 'A3_name_matches_file']),
('B_frontmatter', 35, ['B1_name_present', 'B2_description_present', 'B3_argument_hint', 'B4_allowed_tools', 'B5_component_type']),
('C_specification', 30, ['C1_usage_section', 'C2_arguments_table', 'C3_usage_example', 'C4_workflow_steps']),
('D_documentation', 20, ['D1_related_components', 'D2_markdown_headings', 'D3_code_blocks']),
]
total_base, category_scores = compute_weighted_score(scores, categories)

return {
'name': cmd_name,
'scores': scores,
'category_scores': category_scores,
'total_base': total_base,
'grade': grade_from_score(total_base),
'word_count': word_count,
'track': fm.get('track', 'N/A'),
}

def main(): parser = argparse.ArgumentParser(description='Grade CODITECT commands') parser.add_argument('path', nargs='?', default=str(COMMANDS_DIR), help='Command file or directory') parser.add_argument('--json', dest='json_output', help='Output JSON to file') parser.add_argument('--verbose', action='store_true') args = parser.parse_args()

target = Path(args.path)
if target.is_file():
cmd_files = [target]
else:
cmd_files = sorted([f for f in target.glob('*.md') if f.name != 'README.md'])

results = []
errors = []
for filepath in cmd_files:
try:
results.append(grade_command(str(filepath)))
except Exception as e:
errors.append({'file': filepath.name, 'error': str(e)})

data = aggregate_results(results, 'commands')
data['errors'] = errors

if args.json_output:
output_results(data, args.json_output, 'json')
output_results(data, format='summary')

if args.verbose:
sorted_results = sorted(results, key=lambda x: x['total_base'], reverse=True)
print(f"\nTOP 10:")
for r in sorted_results[:10]:
print(f" {r['grade']} {r['total_base']:5.1f}% | {r['name']}")
print(f"\nBOTTOM 10:")
for r in sorted_results[-10:]:
print(f" {r['grade']} {r['total_base']:5.1f}% | {r['name']}")

if name == 'main': main()