feat: implement Issue #148 core infrastructure for explode-implode variants

Complete implementation of Phase 1 core infrastructure:

Core Infrastructure Components:
- ExplodeVariant enum (flat, hierarchical, semantic)
- ExplodeMode, ManifestVersion, DetectionConfidence enums
- BaseVariant abstract class with common interface
- ExplodeOptions, ImplodeOptions, ExplodeResult, ImplodeResult dataclasses

Manifest System:
- ManifestManager class for manifest.md creation and parsing
- StructureEntry and ManifestData dataclasses
- YAML front matter with complete metadata preservation
- Validation and update mechanisms

Variant Detection:
- VariantDetector class with multiple detection strategies
- Manifest-based detection (highest priority)
- Directory naming pattern recognition
- Semantic structure analysis with confidence scoring
- Automatic fallback and combination logic

Command Interface Updates:
- md-explode: Added --variant parameter with [flat|hierarchical|semantic]
- md-explode: Added --create-manifest/--no-manifest option
- md-implode: Added --force-variant parameter for manual override
- md-implode: Integrated auto-detection with verbose output
- Updated help text and examples for both commands

Test Coverage:
- Comprehensive test suite with 21 test cases
- Tests for all enums, dataclasses, and core functionality
- ManifestManager creation, reading, and validation tests
- VariantDetector pattern recognition and confidence tests
- 100% test pass rate with robust edge case handling

Infrastructure Features:
- Backward compatibility maintained (flat variant default)
- Graceful handling of unimplemented variants with user warnings
- Extensible design for easy addition of new variants
- Clear separation between infrastructure and implementation

Success Criteria Met:
 ExplodeVariant enum with all planned variants
 ManifestManager creates and parses manifest.md files
 Commands accept variant parameters
 Auto-detection logic identifies variant types
 Unit tests achieve 100% pass rate
 Backward compatibility maintained

Ready for Phase 2: Variant implementations (Issue #149)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-12 20:17:41 +02:00
parent 9c8583c77a
commit a17c362653
7 changed files with 1573 additions and 9 deletions

View File

@@ -1733,43 +1733,68 @@ def explode_markdown_file(input_file, output_dir):
@click.argument('input_file', type=click.Path(exists=True))
@click.option('--output-dir', '-o', type=click.Path(),
help='Output directory for exploded files (default: <filename>_exploded)')
@click.option('--variant', type=click.Choice(['flat', 'hierarchical', 'semantic']),
default='flat', help='Directory organization variant (default: flat)')
@click.option('--max-depth', type=int, default=10,
help='Maximum directory nesting depth (default: 10)')
@click.option('--create-manifest/--no-manifest', default=True,
help='Create manifest.md for reversibility (default: true)')
@click.option('--dry-run', is_flag=True,
help='Show what would be done without creating files')
@click.option('--verbose', '-v', is_flag=True,
help='Show detailed output during processing')
@click.pass_context
def md_explode_command(ctx, input_file, output_dir, max_depth, dry_run, verbose):
def md_explode_command(ctx, input_file, output_dir, variant, max_depth, create_manifest, dry_run, verbose):
"""
Explode a markdown file into a directory structure.
Takes a markdown file with hierarchical headings (# ## ### etc.) and creates
a directory structure where each heading becomes a directory or file, with
content distributed appropriately.
content distributed appropriately. Supports multiple organization variants
for different use cases.
INPUT_FILE: Path to the markdown file to explode
Variants:
flat: Current default - creates directories based on h1 headings
hierarchical: Numbered structure reflecting heading hierarchy
semantic: Content-based grouping (parts, chapters, appendices)
Examples:
# Explode book.md into book_exploded/ directory
# Explode book.md into book_exploded/ directory (flat structure)
markitect md-explode book.md
# Use hierarchical structure with numbered directories
markitect md-explode book.md --variant hierarchical
# Explode into custom output directory
markitect md-explode book.md --output-dir /path/to/chapters
# Preview what would be created
markitect md-explode book.md --dry-run --verbose
markitect md-explode book.md --dry-run --verbose --variant semantic
# Explode without creating manifest (legacy mode)
markitect md-explode book.md --no-manifest
"""
config = ctx.obj or {}
try:
input_path = Path(input_file)
# Note: Variant system infrastructure is in place, but only 'flat' is currently implemented
# hierarchical and semantic variants will be implemented in Phase 2 (Issue #149)
if variant != 'flat':
click.echo(f"⚠️ Warning: '{variant}' variant not yet implemented. Using 'flat' variant.")
click.echo(" Hierarchical and semantic variants coming in Phase 2.")
variant = 'flat'
# Determine output directory
if output_dir:
output_path = Path(output_dir)
else:
output_path = input_path.parent / f"{input_path.stem}_exploded"
# For future: variant-specific naming like book.mdd/
suffix = "_exploded" if variant == 'flat' else ".mdd"
output_path = input_path.parent / f"{input_path.stem}{suffix}"
is_verbose = verbose or config.get('verbose', False)
@@ -2999,6 +3024,8 @@ def cli_implode_directory(input_dir, output_file, dry_run=False, verbose=False,
@click.argument('input_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True))
@click.option('--output', '-o', type=click.Path(),
help='Output markdown file (default: <dirname>_imploded.md)')
@click.option('--force-variant', type=click.Choice(['flat', 'hierarchical', 'semantic']),
help='Force specific variant instead of auto-detection')
@click.option('--dry-run', is_flag=True,
help='Preview what would be created without writing files')
@click.option('--verbose', '-v', is_flag=True,
@@ -3010,25 +3037,35 @@ def cli_implode_directory(input_dir, output_file, dry_run=False, verbose=False,
@click.option('--preserve-front-matter/--no-front-matter', default=True,
help='Preserve YAML front matter from files (default: preserve)')
@click.pass_context
def md_implode_command(ctx, input_dir, output, dry_run, verbose, overwrite,
def md_implode_command(ctx, input_dir, output, force_variant, dry_run, verbose, overwrite,
section_spacing, preserve_front_matter):
"""
Implode a directory structure back into a single markdown file.
Takes a directory structure (like one created by md-explode) and combines
all markdown files back into a single document, reconstructing the original
hierarchical heading structure.
hierarchical heading structure. Automatically detects the variant used
during explosion for optimal reconstruction.
INPUT_DIR: Path to the directory to implode
Auto-Detection:
The command automatically detects the variant type by analyzing:
- manifest.md file (highest priority)
- Directory naming patterns
- Content organization structure
Examples:
# Implode exploded directory back to markdown
# Implode exploded directory back to markdown (auto-detect variant)
markitect md-implode book_exploded/
# Force specific variant instead of auto-detection
markitect md-implode chapters/ --force-variant hierarchical
# Specify custom output file
markitect md-implode chapters/ --output reconstructed.md
# Preview what would be created
# Preview what would be created with detection info
markitect md-implode content/ --dry-run --verbose
"""
config = ctx.obj or {}
@@ -3036,6 +3073,43 @@ def md_implode_command(ctx, input_dir, output, dry_run, verbose, overwrite,
try:
input_path = Path(input_dir)
# Auto-detect variant unless forced
detected_variant = None
detection_info = None
if force_variant:
detected_variant = force_variant
detection_info = f"Forced variant: {force_variant}"
else:
try:
# Import here to avoid circular imports during command registration
from markitect.explode_variants import VariantDetector
detector = VariantDetector()
detection_result = detector.detect_variant(input_path)
if detection_result.variant:
detected_variant = detection_result.variant.value
detection_info = f"Auto-detected: {detection_result.variant.value} (confidence: {detection_result.confidence.value})"
if verbose:
click.echo(f"🔍 {detection_info}")
for evidence in detection_result.evidence:
click.echo(f"{evidence}")
else:
detected_variant = 'flat' # fallback
detection_info = "Fallback to flat variant (no clear patterns detected)"
if verbose:
click.echo(f"⚠️ {detection_info}")
except ImportError:
detected_variant = 'flat' # fallback if variant system not available
detection_info = "Using flat variant (variant system not available)"
# Note: Currently only flat variant is implemented
if detected_variant != 'flat':
click.echo(f"⚠️ Warning: '{detected_variant}' variant detected but not yet implemented.")
click.echo(" Using 'flat' variant for now. Full variant support coming in Phase 2.")
detected_variant = 'flat'
# Determine output file
if output:
output_path = Path(output)