feat: add Phase 2 schema refinement tools (schema-analyze and schema-refine)

Implemented two new CLI commands for schema analysis and refinement:

1. schema-analyze: Analyzes schemas for rigidity issues
   - Detects exact counts that should be ranges
   - Identifies missing classification system
   - Flags deprecated extensions
   - Calculates rigidity score (0-100)
   - Provides detailed or summary reports

2. schema-refine: Automatically refines rigid schemas
   - Converts exact counts to flexible ranges
   - Rounds overly specific numbers
   - Widens narrow integer constraints
   - Supports dry-run mode
   - Can save to new file or overwrite in place

Key improvements:
- Created SchemaAnalyzer class with issue detection
- Created SchemaRefiner class with automatic fixes
- Improved schema navigation to handle nested properties
- Tested on example schemas (reduced rigidity from 60/100 to 24/100)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-04 21:29:08 +01:00
parent c46d9f7a0b
commit 2b35fcde62
3 changed files with 855 additions and 0 deletions

View File

@@ -1872,6 +1872,89 @@ def schema_delete(config, schema_name, confirm):
sys.exit(1)
@cli.command('schema-analyze')
@click.argument('schema_file', type=click.Path(exists=True))
@click.option('--verbose', '-v', is_flag=True, help='Show detailed analysis')
@pass_config
def schema_analyze_cmd(config, schema_file, verbose):
"""
Analyze a schema for rigidity issues and suggest improvements.
Examines JSON schemas to detect:
- Exact counts that should be ranges
- Missing classification system
- Deprecated extensions
- Overly specific constraints
Returns exit code 0 for flexible schemas, 1 for rigid schemas, 2 for errors.
Examples:
markitect schema-analyze schema.json
markitect schema-analyze schema.json --verbose
"""
from .schema_analyzer import analyze_schema_cli
sys.exit(analyze_schema_cli(schema_file, verbose=verbose))
@cli.command('schema-refine')
@click.argument('schema_file', type=click.Path(exists=True))
@click.option('--output', '-o', type=click.Path(),
help='Output file (default: overwrite input file)')
@click.option('--loosen-counts', is_flag=True, default=True,
help='Convert exact counts to flexible ranges (default: enabled)')
@click.option('--no-loosen-counts', is_flag=True,
help='Disable count loosening')
@click.option('--round-numbers', is_flag=True, default=True,
help='Round overly specific numbers (default: enabled)')
@click.option('--no-round-numbers', is_flag=True,
help='Disable number rounding')
@click.option('--migrate-deprecated', is_flag=True, default=False,
help='Migrate deprecated extensions (requires manual review)')
@click.option('--dry-run', is_flag=True,
help='Show changes without applying them')
@pass_config
def schema_refine_cmd(config, schema_file, output, loosen_counts, no_loosen_counts,
round_numbers, no_round_numbers, migrate_deprecated, dry_run):
"""
Refine a schema by automatically applying fixes for rigidity issues.
This command analyzes the schema and applies automatic fixes:
- Converts exact counts to flexible ranges
- Rounds overly specific numbers
- Widens narrow integer constraints
- Documents deprecated extension usage
By default, the input file is overwritten. Use --output to save to a different file.
Examples:
# Refine schema in place
markitect schema-refine schema.json
# Preview changes without applying
markitect schema-refine schema.json --dry-run
# Save refined schema to new file
markitect schema-refine schema.json --output refined-schema.json
# Disable specific refinements
markitect schema-refine schema.json --no-loosen-counts
"""
from .schema_refiner import refine_schema_cli
# Handle flag conflicts
loosen = loosen_counts and not no_loosen_counts
round_nums = round_numbers and not no_round_numbers
sys.exit(refine_schema_cli(
schema_file,
output=output,
loosen_counts=loosen,
migrate_deprecated=migrate_deprecated,
round_numbers=round_nums,
dry_run=dry_run
))
@cli.command('generate-stub')
@click.argument('schema_file', type=click.Path(exists=True, path_type=Path))
@click.option('--output', '-o', type=click.Path(path_type=Path),