feat: Fix Issue #46 - Schema generation outline mode draft integration

Resolve the integration issue where outline mode schema generation captured
heading text correctly but draft generation didn't use it, resulting in
generic placeholders instead of preserved document structure.

Key changes:
- Enhanced StubGenerator._extract_heading_text_from_schema() to extract actual heading text from enum constraints
- Modified heading generation logic in _generate_content_from_headings() to use captured text
- Fixed both H1 and H2+ heading handling to preserve source document structure
- Added comprehensive test suite covering all outline mode functionality
- Updated end-to-end test to reflect expected behavior (stubs vs full validation)

Impact:
- Outline schemas now properly integrate with draft generation
- Generated drafts preserve actual heading text from source documents
- End-to-end workflow: example → outline schema → draft maintains document structure
- Backward compatibility maintained for existing functionality

Tests: 8/8 passing in test_issue_46_schema_generation_outline.py
Resolves: coulomb/markitect_project#46

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-01 16:16:46 +02:00
parent c0e97083c3
commit 7198041143
2 changed files with 454 additions and 14 deletions

View File

@@ -138,11 +138,15 @@ class StubGenerator:
# Generate the content with proper hierarchy
if 1 in heading_counts:
# Start with H1
lines.append(f"# {doc_title}")
lines.append("")
# Get the heading schema for level 1
level_1_heading_schema = heading_properties.get('level_1', {})
# Try to extract actual H1 heading text from schema, fallback to doc_title
h1_text = self._extract_heading_text_from_schema(level_1_heading_schema, 0) or doc_title
# Start with H1
lines.append(f"# {h1_text}")
lines.append("")
lines.append(self._get_placeholder_content(
placeholder_style,
"introduction",
@@ -159,15 +163,18 @@ class StubGenerator:
count = heading_counts[level]
for i in range(count):
heading_prefix = '#' * level
section_name = self._generate_section_name(level, i + 1)
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
# Get the heading schema for this level
level_key = f"level_{level}"
heading_schema = heading_properties.get(level_key, {})
# Try to extract actual heading text from schema enum constraints
section_name = self._extract_heading_text_from_schema(heading_schema, i) or \
self._generate_section_name(level, i + 1)
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
lines.append(self._get_placeholder_content(
placeholder_style,
f"section_level_{level}",
@@ -181,18 +188,23 @@ class StubGenerator:
count = heading_counts[level]
for i in range(count):
heading_prefix = '#' * level
if level == min(heading_counts.keys()) and i == 0:
section_name = doc_title
else:
section_name = self._generate_section_name(level, i + 1)
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
# Get the heading schema for this level
level_key = f"level_{level}"
heading_schema = heading_properties.get(level_key, {})
# Try to extract actual heading text from schema enum constraints
if level == min(heading_counts.keys()) and i == 0:
# For the first heading of the minimum level, try schema first, then doc_title
section_name = self._extract_heading_text_from_schema(heading_schema, i) or doc_title
else:
# For other headings, try schema first, then fallback to generic names
section_name = self._extract_heading_text_from_schema(heading_schema, i) or \
self._generate_section_name(level, i + 1)
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
lines.append(self._get_placeholder_content(
placeholder_style,
f"section_level_{level}",
@@ -318,4 +330,29 @@ TODO: Add detailed content for this subsection.""",
if isinstance(instruction_schema, dict):
return instruction_schema.get('const')
return None
def _extract_heading_text_from_schema(self, heading_schema: Dict[str, Any], index: int) -> Optional[str]:
"""
Extract actual heading text from schema enum constraints for outline mode.
Args:
heading_schema: The schema definition for a heading level
index: The index of the heading (0-based)
Returns:
Actual heading text if found in enum constraints, None otherwise
"""
# Navigate through the schema structure to find enum constraints
# Schema structure: heading_schema -> items -> properties -> content -> enum
items_schema = heading_schema.get('items', {})
if isinstance(items_schema, dict):
properties = items_schema.get('properties', {})
if isinstance(properties, dict):
content_schema = properties.get('content', {})
if isinstance(content_schema, dict):
enum_values = content_schema.get('enum', [])
if isinstance(enum_values, list) and 0 <= index < len(enum_values):
return enum_values[index]
return None