feat: complete schema-evolution topic with ADR schema and markdown support
This commit closes the schema-evolution topic (260105) by adding the final deliverable (ADR schema) and fixing markdown schema support across commands. **ADR Schema Created**: - Comprehensive Architecture Decision Record validation schema - 12 section classifications (7 required, 2 recommended, 2 optional, 3 improper/discouraged) - Content pattern validation for ADR formatting rules (status dates, decision statements, rationale structure) - Quality metrics for completeness (word counts, sentence counts) - Follows title case naming convention (Status, Context, Decision, etc.) **Markdown Schema Support Fixed**: - Fixed `markitect validate` command to support .md schemas - Added load_schema_from_path() for both .json and .md files - Updated structural and semantic validation to use schema dict - Fixed `markitect generate-stub` command to support .md schemas - Uses load_schema_from_path() instead of direct JSON loading - Created DocumentWrapper class in semantic_validator.py - Extracts headings from AST tokens (heading_open, inline) - Provides get_headings_by_level() interface expected by validators - Enables section validation to work with real documents **Topic Closure**: - Updated SCHEMA_EVOLUTION_WORKPLAN.md with completion summary - Phases 1-3: 100% complete (via Schema-of-Schemas and Semantic Validation) - Phase 4: Deferred as future enhancement (15-20 sessions) - Phase 5: 70% complete (docs done, CI/CD templates deferred) - Created DONE.md with comprehensive task checklist - Generated ADR template stub (examples/templates/adr-template.md) - Moved topic from roadmap/ to history/260105-schema-evolution/ **Files Changed**: - markitect/cli.py: Added markdown schema support to validate and generate-stub - markitect/semantic_validator.py: Added DocumentWrapper class for AST parsing - markitect/schemas/adr-schema-v1.0.md: New ADR validation schema (560 lines) - examples/templates/adr-template.md: Generated ADR template stub - history/260105-schema-evolution/: Moved completed topic to history **Status**: Schema evolution topic successfully closed with ADR schema as final deliverable. All schema commands now support markdown schemas. Section validation working correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,78 @@ from markitect.validators.link_validator import (
|
||||
)
|
||||
|
||||
|
||||
class DocumentWrapper:
|
||||
"""
|
||||
Wrapper for document dict to provide expected interface for validators.
|
||||
|
||||
Extracts headings from AST and provides get_headings_by_level() method.
|
||||
"""
|
||||
|
||||
def __init__(self, doc_dict: Dict[str, Any]):
|
||||
"""Initialize wrapper with document dict from DocumentManager."""
|
||||
self.doc_dict = doc_dict
|
||||
self._headings_cache = None
|
||||
self._extract_headings()
|
||||
|
||||
def _extract_headings(self):
|
||||
"""Extract headings from AST and cache them."""
|
||||
ast = self.doc_dict.get('ast', [])
|
||||
headings = []
|
||||
|
||||
# Parse AST tokens to find headings
|
||||
# AST format: heading_open, inline (with content), heading_close
|
||||
i = 0
|
||||
while i < len(ast):
|
||||
token = ast[i]
|
||||
if isinstance(token, dict) and token.get('type') == 'heading_open':
|
||||
level_str = token.get('tag', 'h1')[1:] # 'h2' -> '2'
|
||||
level = int(level_str) if level_str.isdigit() else 1
|
||||
|
||||
# Next token should be inline with heading content
|
||||
if i + 1 < len(ast) and ast[i + 1].get('type') == 'inline':
|
||||
content = ast[i + 1].get('content', '')
|
||||
line_number = token.get('map', [0])[0] + 1 if token.get('map') else None
|
||||
|
||||
headings.append({
|
||||
'content': content,
|
||||
'level': level,
|
||||
'line_number': line_number
|
||||
})
|
||||
i += 1
|
||||
|
||||
self._headings_cache = headings
|
||||
|
||||
def get_headings_by_level(self, level: int) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get headings at specified level.
|
||||
|
||||
Args:
|
||||
level: Heading level (1-6)
|
||||
|
||||
Returns:
|
||||
List of heading dicts with 'content', 'level', 'line_number'
|
||||
"""
|
||||
if self._headings_cache is None:
|
||||
self._extract_headings()
|
||||
|
||||
return [h for h in self._headings_cache if h.get('level') == level]
|
||||
|
||||
@property
|
||||
def headings(self) -> List[Dict[str, Any]]:
|
||||
"""Get all headings."""
|
||||
if self._headings_cache is None:
|
||||
self._extract_headings()
|
||||
return self._headings_cache
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Allow dict-like access for compatibility."""
|
||||
return self.doc_dict[key]
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Allow dict-like get for compatibility."""
|
||||
return self.doc_dict.get(key, default)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SemanticValidationReport:
|
||||
"""
|
||||
@@ -238,7 +310,8 @@ class SemanticValidator:
|
||||
doc_manager = DocumentManager()
|
||||
doc = doc_manager.ingest_file(document_path)
|
||||
|
||||
return doc
|
||||
# Wrap in DocumentWrapper to provide expected interface
|
||||
return DocumentWrapper(doc)
|
||||
|
||||
|
||||
def load_schema_from_path(schema_path: str | Path) -> Dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user