feat: Implement Issue #55 - Schema-based draft generation with content instructions
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled

This implementation enhances the existing generate-stub command to utilize
content field instructions from schemas, providing guided document generation
with specific placeholder text instead of generic "TODO" messages.

## Key Features Added:

### Enhanced Schema-Based Generation
- Content instructions from schemas (x-markitect-content-instructions) are now used
- Schema reference metadata included in generated drafts for traceability
- Intelligent fallback to generic placeholders for schemas without instructions
- Full integration with existing generate-stub CLI command and options

### StubGenerator Enhancements
- New _extract_content_instruction_from_heading_schema method for instruction parsing
- Enhanced _get_placeholder_content method with schema-aware content generation
- Updated method signatures to support schema_file_path parameter throughout
- Robust handling of both content instruction and legacy schema formats

### CLI Integration
- Updated generate-stub command documentation with content instruction examples
- Enhanced help text explaining automatic content instruction usage
- Fixed output file generation to include schema references correctly
- Maintained full backward compatibility with existing usage patterns

### Technical Implementation
- Schema reference comments (<!-- Generated from schema: path -->) in generated drafts
- Content instruction text extracted from x-markitect-content-instructions fields
- Support for all instruction types (description, example, constraint, template)
- Integration with existing heading hierarchy and placeholder style systems

## Integration and Compatibility:
- Seamless integration with Issue #54 content field instructions
- Full backward compatibility with existing schemas and usage
- Works with outline mode schemas and heading text capture features
- Comprehensive error handling and graceful degradation

## Testing and Validation:
- Comprehensive test suite covering all acceptance criteria
- Integration tests with schema-generate → generate-stub workflow
- Validation of schema reference metadata and content instruction usage
- Backward compatibility testing with legacy schemas

This completes Issue #55 with full feature implementation, comprehensive testing,
and enhanced documentation for schema-based draft generation capabilities.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-01 08:41:28 +02:00
parent c89a26f6d4
commit 3034b90a0e
3 changed files with 730 additions and 15 deletions

View File

@@ -34,7 +34,8 @@ class StubGenerator:
def generate_stub_from_schema(self, schema: Dict[str, Any],
placeholder_style: str = 'default',
title: Optional[str] = None) -> str:
title: Optional[str] = None,
schema_file_path: Optional[str] = None) -> str:
"""
Generate a markdown stub from a JSON schema dictionary.
@@ -42,6 +43,7 @@ class StubGenerator:
schema: JSON schema as dictionary
placeholder_style: Style of placeholder content ('default', 'custom', 'detailed')
title: Custom title for the document (overrides schema title)
schema_file_path: Optional path to schema file for reference metadata
Returns:
Generated markdown content as string
@@ -49,9 +51,17 @@ class StubGenerator:
# Extract title
doc_title = title or schema.get('title', DEFAULT_TITLE)
# Check if schema has content instructions enabled
content_instructions_enabled = schema.get('x-markitect-content-instructions-enabled', False)
# Start building the markdown content
lines = []
# Add schema reference metadata if schema file path is provided
if schema_file_path:
lines.append(f"<!-- Generated from schema: {schema_file_path} -->")
lines.append("")
# Extract heading structure from schema
headings_schema = schema.get('properties', {}).get('headings', {})
heading_properties = headings_schema.get('properties', {})
@@ -60,12 +70,12 @@ class StubGenerator:
# Create a minimal document if no heading structure is defined
lines.append(f"# {doc_title}")
lines.append("")
lines.append(self._get_placeholder_content(placeholder_style, "main"))
lines.append(self._get_placeholder_content(placeholder_style, "main", schema=schema))
lines.append("")
else:
# Generate content based on heading structure
lines.extend(self._generate_content_from_headings(
heading_properties, doc_title, placeholder_style
heading_properties, doc_title, placeholder_style, schema=schema
))
return '\n'.join(lines)
@@ -90,12 +100,13 @@ class StubGenerator:
with open(schema_file, 'r', encoding='utf-8') as f:
schema = json.load(f)
return self.generate_stub_from_schema(schema)
return self.generate_stub_from_schema(schema, schema_file_path=str(schema_file))
def generate_stub_to_file(self, schema: Dict[str, Any],
output_file: Path,
placeholder_style: str = 'default',
title: Optional[str] = None) -> None:
title: Optional[str] = None,
schema_file_path: Optional[str] = None) -> None:
"""
Generate a markdown stub and save it to a file.
@@ -104,8 +115,9 @@ class StubGenerator:
output_file: Path where to save the generated markdown
placeholder_style: Style of placeholder content
title: Custom title for the document
schema_file_path: Optional path to schema file for reference metadata
"""
content = self.generate_stub_from_schema(schema, placeholder_style, title)
content = self.generate_stub_from_schema(schema, placeholder_style, title, schema_file_path)
# Ensure parent directory exists
output_file.parent.mkdir(parents=True, exist_ok=True)
@@ -114,7 +126,7 @@ class StubGenerator:
f.write(content)
def _generate_content_from_headings(self, heading_properties: Dict[str, Any],
doc_title: str, placeholder_style: str) -> List[str]:
doc_title: str, placeholder_style: str, schema: Optional[Dict[str, Any]] = None) -> List[str]:
"""Generate markdown content from heading structure."""
lines = []
@@ -129,7 +141,14 @@ class StubGenerator:
# Start with H1
lines.append(f"# {doc_title}")
lines.append("")
lines.append(self._get_placeholder_content(placeholder_style, "introduction"))
# Get the heading schema for level 1
level_1_heading_schema = heading_properties.get('level_1', {})
lines.append(self._get_placeholder_content(
placeholder_style,
"introduction",
schema=schema,
heading_schema=level_1_heading_schema
))
lines.append("")
# Generate H2+ headings
@@ -144,7 +163,17 @@ class StubGenerator:
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
lines.append(self._get_placeholder_content(placeholder_style, f"section_level_{level}"))
# Get the heading schema for this level
level_key = f"level_{level}"
heading_schema = heading_properties.get(level_key, {})
lines.append(self._get_placeholder_content(
placeholder_style,
f"section_level_{level}",
schema=schema,
heading_schema=heading_schema
))
lines.append("")
else:
# No H1, start with whatever level is available
@@ -159,7 +188,17 @@ class StubGenerator:
lines.append(f"{heading_prefix} {section_name}")
lines.append("")
lines.append(self._get_placeholder_content(placeholder_style, f"section_level_{level}"))
# Get the heading schema for this level
level_key = f"level_{level}"
heading_schema = heading_properties.get(level_key, {})
lines.append(self._get_placeholder_content(
placeholder_style,
f"section_level_{level}",
schema=schema,
heading_schema=heading_schema
))
lines.append("")
return lines
@@ -194,8 +233,15 @@ class StubGenerator:
else:
return f"Section {index}"
def _get_placeholder_content(self, style: str, section_type: str) -> str:
def _get_placeholder_content(self, style: str, section_type: str, schema: Optional[Dict[str, Any]] = None, heading_schema: Optional[Dict[str, Any]] = None) -> str:
"""Get placeholder content based on style and section type."""
# Check if we have content instructions from schema
if schema and heading_schema and schema.get('x-markitect-content-instructions-enabled', False):
content_instruction = self._extract_content_instruction_from_heading_schema(heading_schema)
if content_instruction:
return content_instruction
# Fall back to standard placeholder generation
generator = self.placeholder_styles.get(style, self.placeholder_styles['default'])
return generator(section_type)
@@ -250,4 +296,26 @@ TODO: Add detailed content for this subsection.""",
return detailed_placeholders.get(
section_type,
f"<!-- {section_type.title()} Section -->\nTODO: Add content for {section_type}."
)
)
def _extract_content_instruction_from_heading_schema(self, heading_schema: Dict[str, Any]) -> Optional[str]:
"""
Extract content instruction from a heading schema items definition.
Args:
heading_schema: The schema definition for a heading level
Returns:
Content instruction text if found, None otherwise
"""
# Navigate through the schema structure to find content instructions
# Schema structure: heading_schema -> items -> properties -> x-markitect-content-instructions -> const
items_schema = heading_schema.get('items', {})
if isinstance(items_schema, dict):
properties = items_schema.get('properties', {})
if isinstance(properties, dict):
instruction_schema = properties.get('x-markitect-content-instructions', {})
if isinstance(instruction_schema, dict):
return instruction_schema.get('const')
return None