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:
2026-01-06 12:32:38 +01:00
parent fc828a345b
commit 5e3646fdff
6 changed files with 1098 additions and 23 deletions

View File

@@ -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]: