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:
2026-01-04 21:30:55 +01:00
parent 2b35fcde62
commit 48e0b60be5
2 changed files with 125 additions and 9 deletions

View File

@@ -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
))

View File

@@ -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