Skip to main content

#!/usr/bin/env python3 """ CODITECT Task ID Validator (ADR-074)

Validates task IDs in files and tool call logs. Used for compliance checking and governance enforcement.

Usage: python validate-task-ids.py # Validate task IDs in file python validate-task-ids.py --check-log # Check session log for compliance python validate-task-ids.py --format # Validate single task ID """

import argparse import json import re import sys from pathlib import Path from typing import List, Dict, Tuple, Optional

Task ID regex pattern

TASK_ID_PATTERN = re.compile(r'^[A-H].\d+(.\d+)*$')

Track definitions

TRACKS = { 'A': {'name': 'Backend API', 'agents': ['senior-architect', 'database-architect']}, 'B': {'name': 'Frontend UI', 'agents': ['frontend-react-typescript-expert']}, 'C': {'name': 'DevOps/Infrastructure', 'agents': ['devops-engineer', 'cloud-architect']}, 'D': {'name': 'Security', 'agents': ['security-specialist']}, 'E': {'name': 'Testing/QA', 'agents': ['testing-specialist']}, 'F': {'name': 'Documentation', 'agents': ['codi-documentation-writer']}, 'G': {'name': 'DMS', 'agents': ['prompt-analyzer-specialist']}, 'H': {'name': 'Innovation/Research', 'agents': ['senior-architect']} }

def validate_task_id(task_id: str) -> Tuple[bool, str, Optional[Dict]]: """Validate a task ID.

Args:
task_id: Task ID to validate (e.g., "A.9.1.3")

Returns:
(is_valid, message, metadata)
"""
if not task_id:
return False, "Empty task ID", None

task_id = task_id.strip()

if not TASK_ID_PATTERN.match(task_id):
return False, f"Invalid format: '{task_id}'. Expected: Track.Section.Task (e.g., A.9.1.3)", None

track = task_id[0]
if track not in TRACKS:
return False, f"Unknown track: '{track}'", None

# Parse components
parts = task_id.split('.')
metadata = {
'track': track,
'track_name': TRACKS[track]['name'],
'section': int(parts[1]) if len(parts) > 1 else None,
'task': int(parts[2]) if len(parts) > 2 else None,
'subtask': int(parts[3]) if len(parts) > 3 else None,
'depth': len(parts),
'suggested_agents': TRACKS[track]['agents']
}

return True, f"Valid task ID: {task_id} ({metadata['track_name']})", metadata

def extract_task_ids_from_text(text: str) -> List[str]: """Extract all task IDs from text.""" # Pattern to find task IDs in context (e.g., "A.9.1.3:" or "[A.9.1.3]") pattern = r'\b([A-H].\d+(?:.\d+)*)\b' matches = re.findall(pattern, text) return list(set(matches))

def validate_file(file_path: Path) -> Dict: """Validate task IDs in a file.

Returns:
Validation report
"""
content = file_path.read_text()
task_ids = extract_task_ids_from_text(content)

results = {
'file': str(file_path),
'total_task_ids': len(task_ids),
'valid': [],
'invalid': [],
'tracks_used': set()
}

for task_id in task_ids:
is_valid, message, metadata = validate_task_id(task_id)
if is_valid:
results['valid'].append({'id': task_id, 'metadata': metadata})
results['tracks_used'].add(metadata['track'])
else:
results['invalid'].append({'id': task_id, 'error': message})

results['tracks_used'] = list(results['tracks_used'])
return results

def check_session_log(log_path: Path) -> Dict: """Check a session log for task ID compliance.

Returns:
Compliance report
"""
lines = log_path.read_text().splitlines()

results = {
'file': str(log_path),
'total_tool_calls': 0,
'compliant_calls': 0,
'non_compliant_calls': 0,
'violations': [],
'compliance_rate': 0.0
}

for i, line in enumerate(lines):
# Look for tool calls (various formats)
if 'Bash(' in line or 'Edit(' in line or 'Write(' in line or 'Read(' in line:
results['total_tool_calls'] += 1

# Check for task ID in description
task_ids = extract_task_ids_from_text(line)
if task_ids:
results['compliant_calls'] += 1
else:
results['non_compliant_calls'] += 1
results['violations'].append({
'line': i + 1,
'content': line[:100] + ('...' if len(line) > 100 else '')
})

if results['total_tool_calls'] > 0:
results['compliance_rate'] = (results['compliant_calls'] / results['total_tool_calls']) * 100

return results

def print_report(report: Dict, verbose: bool = False): """Print validation report.""" print(f"\n{'='*60}") print(f"Task ID Validation Report") print(f"{'='*60}") print(f"File: {report['file']}")

if 'compliance_rate' in report:
# Session log compliance report
print(f"\nTool Call Compliance:")
print(f" Total calls: {report['total_tool_calls']}")
print(f" Compliant: {report['compliant_calls']}")
print(f" Violations: {report['non_compliant_calls']}")
print(f" Rate: {report['compliance_rate']:.1f}%")

if report['violations'] and verbose:
print(f"\nViolations:")
for v in report['violations'][:10]: # Limit to 10
print(f" Line {v['line']}: {v['content']}")
else:
# File validation report
print(f"\nTask IDs Found: {report['total_task_ids']}")
print(f" Valid: {len(report['valid'])}")
print(f" Invalid: {len(report['invalid'])}")
print(f" Tracks: {', '.join(report['tracks_used']) or 'None'}")

if report['invalid']:
print(f"\nInvalid Task IDs:")
for item in report['invalid']:
print(f" - {item['id']}: {item['error']}")

if verbose and report['valid']:
print(f"\nValid Task IDs:")
for item in report['valid']:
m = item['metadata']
print(f" - {item['id']}: {m['track_name']} (depth: {m['depth']})")

def main(): parser = argparse.ArgumentParser( description="Validate CODITECT task IDs", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python validate-task-ids.py plan.md python validate-task-ids.py --check-log session.jsonl python validate-task-ids.py --format A.9.1.3 python validate-task-ids.py --tracks """ )

parser.add_argument('file', nargs='?', help='File to validate')
parser.add_argument('--format', metavar='ID', help='Validate a single task ID')
parser.add_argument('--check-log', metavar='LOG', help='Check session log for compliance')
parser.add_argument('--tracks', action='store_true', help='List all tracks')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
parser.add_argument('--json', action='store_true', help='Output as JSON')

args = parser.parse_args()

# List tracks
if args.tracks:
print("\nCODITECT Track Nomenclature:")
print("="*50)
for track, info in TRACKS.items():
print(f" {track}: {info['name']}")
print(f" Agents: {', '.join(info['agents'])}")
return 0

# Validate single task ID
if args.format:
is_valid, message, metadata = validate_task_id(args.format)
if args.json:
print(json.dumps({'valid': is_valid, 'message': message, 'metadata': metadata}))
else:
status = "VALID" if is_valid else "INVALID"
print(f"[{status}] {message}")
if metadata:
print(f" Track: {metadata['track']} - {metadata['track_name']}")
print(f" Depth: {metadata['depth']}")
print(f" Suggested agents: {', '.join(metadata['suggested_agents'])}")
return 0 if is_valid else 1

# Check session log
if args.check_log:
log_path = Path(args.check_log)
if not log_path.exists():
print(f"Error: Log file not found: {log_path}", file=sys.stderr)
return 1
report = check_session_log(log_path)
if args.json:
print(json.dumps(report, indent=2))
else:
print_report(report, args.verbose)
return 0 if report['compliance_rate'] >= 80 else 1

# Validate file
if args.file:
file_path = Path(args.file)
if not file_path.exists():
print(f"Error: File not found: {file_path}", file=sys.stderr)
return 1
report = validate_file(file_path)
if args.json:
print(json.dumps(report, indent=2))
else:
print_report(report, args.verbose)
return 0 if not report['invalid'] else 1

parser.print_help()
return 0

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