feat: add interactive mode to schema-refine command
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
))
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user