scripts-domain
""" Domain Judge Agent
Validates classifications against CODITECT domain conventions. Checks for:
- Valid document type for CODITECT framework
- Classification matches CODITECT naming/structure conventions
- Path-classification alignment
- Semantic appropriateness for the domain """
import re from typing import List, Dict, Set, Optional from pathlib import Path import sys
sys.path.insert(0, str(Path(file).parent.parent))
from core.models import Document, AnalystVote, JudgeDecision, DocumentType from judges.base import BaseJudge
class DomainJudge(BaseJudge): """Judge that validates classifications against CODITECT domain rules.
Ensures classifications are appropriate for the CODITECT framework
and align with established naming conventions and patterns.
"""
name = "domain"
description = "Validates CODITECT domain conventions and classification appropriateness"
has_veto_authority = True
weight = 1.2 # Slightly higher weight for domain expertise
# CODITECT-specific path expectations
PATH_TYPE_EXPECTATIONS: Dict[str, List[str]] = {
'agent': ['/agents/', '/agent-', '-agent.md'],
'command': ['/commands/', '/command-', '-command.md'],
'skill': ['/skills/', 'SKILL.md'],
'workflow': ['/workflows/', '.workflow.', '-workflow.'],
'adr': ['/adrs/', 'ADR-', '/adr-'],
'hook': ['/hooks/', '-hook.', 'hook-'],
'guide': ['/guides/', '-guide.', '-GUIDE.'],
'config': ['/config/', '.json', '.yaml', '.yml'],
'script': ['/scripts/', '.py', '.sh'],
'reference': ['README.md', 'REFERENCE.md', 'INDEX.md', 'CLAUDE.md'],
}
# Types that should NOT be classified as (deprecated/invalid)
INVALID_TYPES: Set[str] = set() # Currently none, but can add
# Expected sections for each type (CODITECT standard)
REQUIRED_SECTIONS: Dict[str, List[str]] = {
'adr': ['Status', 'Context', 'Decision'],
'agent': ['Role', 'Capabilities'],
'command': ['Invocation'],
'skill': ['When to Use'],
}
def evaluate(
self,
document: Document,
votes: List[AnalystVote]
) -> JudgeDecision:
"""Evaluate classification against CODITECT domain rules."""
if not votes:
return self._create_decision(
approved=False,
reason="No analyst votes to evaluate",
confidence=1.0,
metadata={'error': 'no_votes'}
)
consensus = self._get_consensus_classification(votes)
if not consensus:
return self._create_decision(
approved=False,
reason="No consensus classification to validate",
confidence=0.8,
metadata={'error': 'no_consensus'}
)
issues = []
warnings = []
validations = []
# Validation 1: Valid document type
valid_types = [t.value for t in DocumentType]
if consensus not in valid_types:
issues.append(f"'{consensus}' is not a valid CODITECT document type")
else:
validations.append("Valid document type")
# Validation 2: Check for deprecated/invalid types
if consensus in self.INVALID_TYPES:
issues.append(f"'{consensus}' is a deprecated document type")
# Validation 3: Path-type alignment
path_alignment = self._check_path_alignment(document, consensus)
if path_alignment['aligned']:
validations.append(f"Path aligns with '{consensus}' type")
elif path_alignment['conflict']:
expected = path_alignment['expected_type']
warnings.append(
f"Path suggests '{expected}' but classified as '{consensus}'"
)
# Validation 4: Content-type alignment (check required sections)
if consensus in self.REQUIRED_SECTIONS:
section_check = self._check_required_sections(document, consensus)
if section_check['present']:
validations.append(f"Has required {consensus} sections")
elif section_check['missing']:
warnings.append(
f"Missing {consensus} sections: {', '.join(section_check['missing'])}"
)
# Validation 5: Frontmatter type alignment
fm_type = document.frontmatter.get('type', '').lower()
if fm_type:
if fm_type == consensus:
validations.append("Frontmatter type matches consensus")
else:
warnings.append(
f"Frontmatter type '{fm_type}' differs from consensus '{consensus}'"
)
# Validation 6: CODITECT-specific patterns
coditect_check = self._check_coditect_patterns(document, consensus)
if coditect_check['issues']:
for issue in coditect_check['issues']:
warnings.append(issue)
if coditect_check['validations']:
validations.extend(coditect_check['validations'])
# Calculate domain confidence
validation_score = len(validations) / max(1, len(validations) + len(warnings))
# Determine approval
if issues:
return self._create_decision(
approved=False,
reason=f"Domain validation failed: {'; '.join(issues)}",
confidence=0.5,
metadata={
'issues': issues,
'warnings': warnings,
'validations': validations,
'consensus': consensus,
'path': str(document.path)
}
)
# Approved with confidence based on validation ratio
confidence = min(0.98, 0.60 + (validation_score * 0.38))
if warnings:
reason = f"Domain approved with warnings: {consensus} ({len(warnings)} warnings)"
else:
reason = f"Domain approved: {consensus} fully validated"
return self._create_decision(
approved=True,
reason=reason,
confidence=confidence,
metadata={
'warnings': warnings,
'validations': validations,
'consensus': consensus,
'validation_score': round(validation_score, 3),
'path_aligned': path_alignment['aligned']
}
)
def _check_path_alignment(self, document: Document, classification: str) -> Dict:
"""Check if document path aligns with classification."""
path_str = str(document.path).lower()
# Check if path matches expected patterns for this type
if classification in self.PATH_TYPE_EXPECTATIONS:
patterns = self.PATH_TYPE_EXPECTATIONS[classification]
for pattern in patterns:
if pattern.lower() in path_str:
return {'aligned': True, 'conflict': False, 'expected_type': None}
# Check if path suggests a different type
for doc_type, patterns in self.PATH_TYPE_EXPECTATIONS.items():
if doc_type == classification:
continue
for pattern in patterns:
if pattern.lower() in path_str:
return {
'aligned': False,
'conflict': True,
'expected_type': doc_type
}
# No strong signal either way
return {'aligned': False, 'conflict': False, 'expected_type': None}
def _check_required_sections(self, document: Document, classification: str) -> Dict:
"""Check if document has required sections for its type."""
required = self.REQUIRED_SECTIONS.get(classification, [])
if not required:
return {'present': True, 'missing': []}
content = document.content.lower()
missing = []
present = []
for section in required:
# Check for ## Section or # Section patterns
pattern = rf'#{1,2}\s*{section.lower()}'
if re.search(pattern, content):
present.append(section)
else:
missing.append(section)
return {
'present': len(missing) == 0,
'missing': missing,
'found': present
}
def _check_coditect_patterns(self, document: Document, classification: str) -> Dict:
"""Check for CODITECT-specific patterns and conventions."""
issues = []
validations = []
content = document.content
# Check for CODITECT version markers
if 'CODITECT' in content:
validations.append("Contains CODITECT reference")
# Agent-specific checks
if classification == 'agent':
if 'subagent_type' in content or 'Task(' in content:
validations.append("Contains agent invocation patterns")
elif '## Role' not in content and '## Capabilities' not in content:
issues.append("Agent missing Role or Capabilities section")
# ADR-specific checks
if classification == 'adr':
if not re.search(r'ADR-\d+', content):
issues.append("ADR missing ADR-### identifier")
if '**Accepted**' in content or '**Rejected**' in content:
validations.append("Has valid ADR status marker")
# Workflow-specific checks
if classification == 'workflow':
if '```mermaid' in content:
validations.append("Contains Mermaid diagram")
if re.search(r'(Phase|Step|Stage)\s*\d', content, re.IGNORECASE):
validations.append("Contains phased structure")
# Command-specific checks
if classification == 'command':
if re.search(r'^/\w+', content, re.MULTILINE):
validations.append("Contains slash command invocation")
return {
'issues': issues,
'validations': validations
}