orchestrated-discussions/smarttools/discussion-validator/config.yaml

90 lines
3.4 KiB
YAML

# discussion-validator - Validate discussion format and check for issues
# Usage: cat discussion.md | discussion-validator | jq .
name: discussion-validator
description: Validate discussion format and check for issues
category: Discussion
steps:
- type: code
code: |
import re
import json
issues = []
warnings = []
# Check for DISCUSSION marker
if not re.search(r'<!--\s*DISCUSSION\s*-->', input, re.IGNORECASE):
issues.append("Missing <!-- DISCUSSION --> marker")
# Check for required headers
required_headers = ['Title', 'Phase', 'Status']
for header in required_headers:
if not re.search(rf'<!--\s*{header}:\s*.+?\s*-->', input, re.IGNORECASE):
issues.append(f"Missing required header: {header}")
# Check for valid phase
phase_match = re.search(r'<!--\s*Phase:\s*(\w+)\s*-->', input, re.IGNORECASE)
if phase_match:
valid_phases = [
'initial_feedback', 'detailed_review', 'consensus_vote',
'final_vote', 'signoff', 'completed', 'proposal', 'review'
]
phase = phase_match.group(1)
# Build lowercase list explicitly to avoid exec() scope issues
valid_phases_lower = []
for p in valid_phases:
valid_phases_lower.append(p.lower())
if phase.lower() not in valid_phases_lower:
warnings.append(f"Unknown phase: {phase}")
# Check for valid status
status_match = re.search(r'<!--\s*Status:\s*(\w+)\s*-->', input, re.IGNORECASE)
if status_match:
valid_statuses = [
'OPEN', 'PROPOSED', 'READY_FOR_DESIGN', 'READY_FOR_IMPLEMENTATION',
'READY_FOR_TESTING', 'CLOSED', 'REJECTED'
]
status = status_match.group(1).upper()
if status not in valid_statuses:
warnings.append(f"Unknown status: {status}")
# Check for orphaned mentions (mentioned but no response)
mentions = set(re.findall(r'@(\w+)', input))
# Get responders from Name: lines
responders = set()
for match in re.finditer(r'^Name:\s*(?:AI-)?(\w+)', input, re.MULTILINE | re.IGNORECASE):
responders.add(match.group(1).lower())
pending = mentions - responders - {'all'}
if pending:
warnings.append(f"Pending responses from: {', '.join(sorted(pending))}")
# Check for empty comment blocks
for match in re.finditer(r'^---\s*\n\s*Name:\s*(.+?)\n\s*(?=^---|\Z)', input, re.MULTILINE):
author = match.group(1).strip()
warnings.append(f"Empty comment block from: {author}")
# Check for duplicate votes from same author
votes = {}
for match in re.finditer(r'^---\s*\nName:\s*(.+?)\n.*?VOTE:\s*(READY|CHANGES|REJECT)', input, re.MULTILINE | re.DOTALL | re.IGNORECASE):
author = match.group(1).strip()
vote = match.group(2).upper()
if author in votes and votes[author] != vote:
warnings.append(f"Multiple different votes from {author}: {votes[author]} -> {vote}")
votes[author] = vote
validation = json.dumps({
"valid": len(issues) == 0,
"issues": issues,
"warnings": warnings,
"metadata_found": {
"phase": phase_match.group(1) if phase_match else None,
"status": status_match.group(1) if status_match else None
}
}, indent=2)
output_var: validation
output: "{validation}"