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

@@ -1984,7 +1984,9 @@ def generate_stub(config, schema_file, output, style, title):
Generate a markdown stub/template from a JSON schema.
Creates a markdown document with proper heading hierarchy and placeholder
content based on the structural definitions in the JSON schema.
content based on the structural definitions in the JSON schema. When schemas
include content instructions (x-markitect-content-instructions), the generated
stub will use specific guidance text instead of generic placeholders.
SCHEMA_FILE: Path to the JSON schema file
@@ -1992,6 +1994,19 @@ def generate_stub(config, schema_file, output, style, title):
markitect generate-stub blog_schema.json
markitect generate-stub schema.json --output template.md
markitect generate-stub schema.json --style detailed --title "My Document"
# Content instructions will be used automatically when present in schema
markitect generate-stub schema_with_instructions.json
Content Instructions:
When a schema contains x-markitect-content-instructions-enabled: true,
the generated stub will include specific content guidance from the schema
instead of generic "TODO" placeholders. This is especially useful with
schemas created using the --include-content-instructions option.
Schema Reference:
Generated stubs include a comment referencing the source schema file
for validation and traceability purposes.
"""
try:
if config.get('verbose'):
@@ -2009,7 +2024,7 @@ def generate_stub(config, schema_file, output, style, title):
schema = json.load(f)
stub_content = generator.generate_stub_from_schema(
schema, placeholder_style=style, title=title
schema, placeholder_style=style, title=title, schema_file_path=schema_file
)
# Mode-based output logic
@@ -2021,7 +2036,7 @@ def generate_stub(config, schema_file, output, style, title):
# Output to file or stdout
if output:
generator.generate_stub_to_file(schema, output, style, title)
generator.generate_stub_to_file(schema, output, style, title, schema_file)
click.echo(f"✅ Stub generated: {output}")
if config.get('verbose'):

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