From 48e0b60be584ac54bb015976efdff064f5379224 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 4 Jan 2026 21:30:55 +0100 Subject: [PATCH] feat: add interactive mode to schema-refine command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added --interactive/-i flag to schema-refine command that allows users to review and approve each refinement individually: - Displays each detected issue with details - Shows current and suggested values - Prompts for confirmation (y/N/q) - Applies only approved fixes - Shows summary at completion This gives users fine-grained control over which refinements to apply. Example usage: markitect schema-refine schema.json --interactive 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- markitect/cli.py | 10 ++- markitect/schema_refiner.py | 124 ++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/markitect/cli.py b/markitect/cli.py index 254fc065..6653b3e7 100644 --- a/markitect/cli.py +++ b/markitect/cli.py @@ -1912,9 +1912,11 @@ def schema_analyze_cmd(config, schema_file, verbose): help='Migrate deprecated extensions (requires manual review)') @click.option('--dry-run', is_flag=True, help='Show changes without applying them') +@click.option('--interactive', '-i', is_flag=True, + help='Prompt for each refinement interactively') @pass_config def schema_refine_cmd(config, schema_file, output, loosen_counts, no_loosen_counts, - round_numbers, no_round_numbers, migrate_deprecated, dry_run): + round_numbers, no_round_numbers, migrate_deprecated, dry_run, interactive): """ Refine a schema by automatically applying fixes for rigidity issues. @@ -1933,6 +1935,9 @@ def schema_refine_cmd(config, schema_file, output, loosen_counts, no_loosen_coun # Preview changes without applying markitect schema-refine schema.json --dry-run + # Review each fix interactively + markitect schema-refine schema.json --interactive + # Save refined schema to new file markitect schema-refine schema.json --output refined-schema.json @@ -1951,7 +1956,8 @@ def schema_refine_cmd(config, schema_file, output, loosen_counts, no_loosen_coun loosen_counts=loosen, migrate_deprecated=migrate_deprecated, round_numbers=round_nums, - dry_run=dry_run + dry_run=dry_run, + interactive=interactive )) diff --git a/markitect/schema_refiner.py b/markitect/schema_refiner.py index 46893c05..b27ff0aa 100644 --- a/markitect/schema_refiner.py +++ b/markitect/schema_refiner.py @@ -68,6 +68,89 @@ class SchemaRefiner: else: return None + def refine_schema_interactive( + self, + schema: Dict[str, Any], + loosen_counts: bool = True, + migrate_deprecated: bool = False, + round_numbers: bool = True + ) -> RefinementResult: + """ + Refine a schema interactively, prompting for each fix. + + Args: + schema: The JSON schema to refine + loosen_counts: Enable fixes for exact counts + migrate_deprecated: Enable migration of deprecated extensions + round_numbers: Enable rounding of overly specific numbers + + Returns: + RefinementResult with actions taken and refined schema + """ + result = RefinementResult(success=False) + + try: + # Analyze the schema first + analysis = self.analyzer.analyze_schema(schema) + + print(f"\nFound {len(analysis.issues)} issue(s) to review\n") + + # Deep copy to avoid modifying original + refined = copy.deepcopy(schema) + + # Process each issue interactively + for i, issue in enumerate(analysis.issues, 1): + print(f"Issue {i}/{len(analysis.issues)}") + print(f" Type: {issue.issue_type.value}") + print(f" Path: {issue.path}") + print(f" {issue.message}") + print(f" Suggestion: {issue.suggestion}") + + if issue.current_value is not None: + print(f" Current: {json.dumps(issue.current_value)}") + if issue.suggested_value is not None: + print(f" Suggested: {json.dumps(issue.suggested_value)}") + + # Ask user if they want to apply the fix + response = input("\nApply this fix? [y/N/q]: ").strip().lower() + + if response == 'q': + print("Refinement cancelled by user") + result.success = False + return result + elif response == 'y': + action = None + + if loosen_counts and issue.issue_type == IssueType.EXACT_COUNT: + action = self._fix_exact_count(refined, issue) + + elif round_numbers and issue.issue_type == IssueType.OVERLY_SPECIFIC: + action = self._fix_overly_specific(refined, issue) + + elif loosen_counts and issue.issue_type == IssueType.NO_FLEXIBILITY: + action = self._fix_no_flexibility(refined, issue) + + elif migrate_deprecated and issue.issue_type == IssueType.DEPRECATED_EXTENSIONS: + action = self._fix_deprecated_extension(refined, issue) + + if action: + result.actions_taken.append(action) + print(f" ✓ Applied") + else: + print(f" ✗ Could not apply fix") + else: + print(f" - Skipped") + + print() + + result.refined_schema = refined + result.success = True + + except Exception as e: + result.error_message = str(e) + + return result + def refine_schema( self, schema: Dict[str, Any], @@ -354,7 +437,8 @@ def refine_schema_cli( loosen_counts: bool = True, migrate_deprecated: bool = False, round_numbers: bool = True, - dry_run: bool = False + dry_run: bool = False, + interactive: bool = False ) -> int: """ CLI entry point for schema refinement. @@ -366,6 +450,7 @@ def refine_schema_cli( migrate_deprecated: Migrate deprecated extensions round_numbers: Round overly specific numbers dry_run: Show changes without applying + interactive: Prompt for each fix Returns: Exit code (0 = success, 1 = no changes needed, 2 = error) @@ -376,11 +461,29 @@ def refine_schema_cli( input_path = Path(schema_path) output_path = Path(output) if output else None - if dry_run: - # Just analyze and show what would be done - with open(input_path) as f: - schema = json.load(f) + # Load schema + with open(input_path) as f: + schema = json.load(f) + if interactive: + # Interactive mode - prompt for each fix + print(f"Refining schema: {schema_path}") + result = refiner.refine_schema_interactive( + schema, + loosen_counts=loosen_counts, + migrate_deprecated=migrate_deprecated, + round_numbers=round_numbers + ) + + if result.success and result.refined_schema and not dry_run: + # Write the refined schema + output = output_path or input_path + with open(output, 'w') as f: + json.dump(result.refined_schema, f, indent=2) + print(f"\nRefined schema written to: {output}") + + elif dry_run: + # Just analyze and show what would be done result = refiner.refine_schema( schema, loosen_counts=loosen_counts, @@ -399,8 +502,15 @@ def refine_schema_cli( round_numbers=round_numbers ) - report = refiner.format_refinement_report(result) - print(report) + # Only print full report if not in interactive mode (user already saw changes) + if not interactive: + report = refiner.format_refinement_report(result) + print(report) + elif result.success: + # Just print summary for interactive mode + print(f"\n{'='*70}") + print(f"Refinement complete: {len(result.actions_taken)} change(s) applied") + print(f"{'='*70}") if result.success and len(result.actions_taken) > 0: return 0 # Success with changes