diff --git a/markitect/cli.py b/markitect/cli.py index 93c69c14..39d769c2 100644 --- a/markitect/cli.py +++ b/markitect/cli.py @@ -1493,7 +1493,7 @@ def generate_schema(config, file_path, max_depth, output, outfile, output_format @cli.command('validate') @click.argument('file_path', type=click.Path(exists=True, path_type=Path)) @click.option('--schema', '-s', type=click.Path(exists=True, path_type=Path), - help='Path to JSON schema file') + help='Path to JSON schema file (.json or .md)') @click.option('--schema-json', type=str, help='JSON schema provided as a string') @click.option('--quiet', '-q', is_flag=True, @@ -1502,21 +1502,38 @@ def generate_schema(config, file_path, max_depth, output, outfile, output_format help='Show detailed validation errors (Issue #8)') @click.option('--error-format', type=click.Choice(['text', 'json', 'markdown']), default='text', help='Format for detailed error output') +@click.option('--semantic/--no-semantic', default=True, + help='Enable/disable semantic validation (sections, patterns, quality)') +@click.option('--check-links', is_flag=True, + help='Enable link validation (may be slow, requires --semantic)') +@click.option('--strict', is_flag=True, + help='Treat warnings as errors') @pass_config -def validate(config, file_path, schema, schema_json, quiet, detailed_errors, error_format): +def validate(config, file_path, schema, schema_json, quiet, detailed_errors, error_format, + semantic, check_links, strict): """ Validate a markdown file against a JSON schema. + ENHANCED: Now includes semantic validation of x-markitect extensions: + - Section classifications (required, recommended, optional, discouraged, improper) + - Content patterns (required_patterns, forbidden_patterns) + - Quality metrics (min_words, max_words, min_sentences) + Checks if a markdown document strictly adheres to the structure defined by a specified schema. Returns boolean result (True/False). - Issue #8: Enhanced with detailed error reporting for failed validations. - Examples: - markitect validate doc.md --schema schema.json - markitect validate doc.md --schema-json '{"$schema": "...", "type": "object"}' + # Structural + semantic validation (default) + markitect validate doc.md --schema manpage-schema-v1.0.md + + # Only structural validation + markitect validate doc.md --schema schema.json --no-semantic + + # Strict mode (warnings become errors) + markitect validate doc.md --schema schema.json --strict + + # Legacy detailed errors markitect validate doc.md --schema schema.json --detailed-errors - markitect validate doc.md --schema schema.json --errors --error-format json """ try: validator = SchemaValidator() @@ -1594,6 +1611,46 @@ def validate(config, file_path, schema, schema_json, quiet, detailed_errors, err click.echo("❌ Document structure does not match schema requirements") click.echo("💡 Use --detailed-errors to see specific validation issues") + # Semantic validation (if enabled and schema has x-markitect extensions) + semantic_report = None + if semantic and schema: + try: + from .semantic_validator import SemanticValidator, load_schema_from_path + + # Load schema (supports .md and .json) + schema_dict = load_schema_from_path(schema) + + # Check if schema has x-markitect extensions + has_extensions = ('x-markitect-sections' in schema_dict or + 'x-markitect-content-control' in schema_dict) + + if has_extensions: + sem_validator = SemanticValidator(schema_dict) + semantic_report = sem_validator.validate(file_path, check_links=check_links) + + # Combine with structural validation result + if semantic_report and not quiet: + click.echo("") + click.echo("=" * 60) + click.echo("Semantic Validation Results:") + click.echo("=" * 60) + click.echo(semantic_report.format_text()) + + # Update validity based on semantic validation + if semantic_report: + if semantic_report.has_errors(): + is_valid = False + elif strict and semantic_report.has_warnings(): + is_valid = False + + except Exception as e: + # Semantic validation failure doesn't fail the whole command + # unless strict mode is enabled + if not quiet: + click.echo(f"\n⚠️ Semantic validation error: {e}", err=True) + if strict: + is_valid = False + # Exit with appropriate code sys.exit(0 if is_valid else 1)