django-cfg 1.4.9__py3-none-any.whl → 1.4.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/middleware/api_access.py +6 -2
  8. django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
  9. django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
  10. django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
  11. django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
  12. django_cfg/apps/payments/services/core/balance_service.py +5 -5
  13. django_cfg/apps/payments/services/core/subscription_service.py +1 -2
  14. django_cfg/apps/payments/views/api/balances.py +8 -7
  15. django_cfg/apps/payments/views/api/base.py +10 -6
  16. django_cfg/apps/payments/views/api/currencies.py +53 -10
  17. django_cfg/apps/payments/views/api/payments.py +3 -1
  18. django_cfg/apps/payments/views/api/subscriptions.py +2 -5
  19. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  20. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  21. django_cfg/apps/payments/views/overview/views.py +2 -1
  22. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  23. django_cfg/apps/urls.py +106 -45
  24. django_cfg/core/base/config_model.py +2 -2
  25. django_cfg/core/constants.py +1 -1
  26. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  27. django_cfg/core/generation/integration_generators/api.py +82 -41
  28. django_cfg/core/integration/display/startup.py +30 -22
  29. django_cfg/core/integration/url_integration.py +15 -16
  30. django_cfg/dashboard/sections/documentation.py +391 -0
  31. django_cfg/management/commands/check_endpoints.py +11 -160
  32. django_cfg/management/commands/check_settings.py +13 -265
  33. django_cfg/management/commands/clear_constance.py +13 -201
  34. django_cfg/management/commands/create_token.py +13 -321
  35. django_cfg/management/commands/generate_clients.py +23 -0
  36. django_cfg/management/commands/list_urls.py +13 -306
  37. django_cfg/management/commands/migrate_all.py +13 -126
  38. django_cfg/management/commands/migrator.py +13 -396
  39. django_cfg/management/commands/rundramatiq.py +15 -247
  40. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  41. django_cfg/management/commands/runserver_ngrok.py +15 -160
  42. django_cfg/management/commands/script.py +12 -488
  43. django_cfg/management/commands/show_config.py +12 -215
  44. django_cfg/management/commands/show_urls.py +12 -342
  45. django_cfg/management/commands/superuser.py +15 -295
  46. django_cfg/management/commands/task_clear.py +14 -217
  47. django_cfg/management/commands/task_status.py +13 -248
  48. django_cfg/management/commands/test_email.py +15 -86
  49. django_cfg/management/commands/test_telegram.py +14 -61
  50. django_cfg/management/commands/test_twilio.py +15 -105
  51. django_cfg/management/commands/tree.py +13 -383
  52. django_cfg/management/commands/validate_openapi.py +10 -0
  53. django_cfg/middleware/README.md +1 -1
  54. django_cfg/middleware/user_activity.py +3 -3
  55. django_cfg/models/__init__.py +2 -2
  56. django_cfg/models/api/drf/spectacular.py +6 -6
  57. django_cfg/models/django/__init__.py +2 -2
  58. django_cfg/models/django/openapi.py +238 -0
  59. django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
  60. django_cfg/modules/django_admin/management/__init__.py +0 -0
  61. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  62. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  63. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  64. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  65. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  66. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  67. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  68. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  69. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  70. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  71. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  72. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  73. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  74. django_cfg/modules/django_client/__init__.py +20 -0
  75. django_cfg/modules/django_client/apps.py +35 -0
  76. django_cfg/modules/django_client/core/__init__.py +56 -0
  77. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  78. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  79. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  80. django_cfg/modules/django_client/core/cli/main.py +235 -0
  81. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  82. django_cfg/modules/django_client/core/config/config.py +188 -0
  83. django_cfg/modules/django_client/core/config/group.py +101 -0
  84. django_cfg/modules/django_client/core/config/service.py +209 -0
  85. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  86. django_cfg/modules/django_client/core/generator/base.py +767 -0
  87. django_cfg/modules/django_client/core/generator/python.py +751 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  94. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  95. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  96. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  97. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  98. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  99. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  100. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  102. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  103. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  104. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  105. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  112. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  113. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  114. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  115. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  116. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  117. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  118. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  119. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  120. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  121. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  122. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  123. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  124. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  125. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  126. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  127. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  128. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  129. django_cfg/modules/django_client/core/ir/context.py +387 -0
  130. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  131. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  132. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  133. django_cfg/modules/django_client/core/parser/base.py +648 -0
  134. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  135. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  136. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  137. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  138. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  139. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  140. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  141. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  142. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  143. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  144. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  145. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  146. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  147. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  148. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  149. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  150. django_cfg/modules/django_client/management/__init__.py +3 -0
  151. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  152. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  153. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  154. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  155. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  156. django_cfg/modules/django_client/urls.py +72 -0
  157. django_cfg/modules/django_email/management/__init__.py +0 -0
  158. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  159. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  160. django_cfg/modules/django_logging/django_logger.py +6 -6
  161. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  162. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  164. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  165. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  166. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  167. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  168. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  169. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  170. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  171. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  172. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  173. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  174. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  176. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  177. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  178. django_cfg/modules/django_unfold/dashboard.py +1 -1
  179. django_cfg/pyproject.toml +2 -6
  180. django_cfg/registry/third_party.py +5 -7
  181. django_cfg/routing/callbacks.py +1 -1
  182. django_cfg/static/admin/css/prose-unfold.css +666 -0
  183. django_cfg/templates/admin/index.html +8 -0
  184. django_cfg/templates/admin/index_new.html +13 -0
  185. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  186. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  187. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  188. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  189. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
  190. django_cfg/management/commands/generate.py +0 -107
  191. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  192. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  193. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,134 @@
1
+ """Validation checker orchestrating all rules."""
2
+
3
+ from pathlib import Path
4
+ from typing import List, Optional
5
+
6
+ from .rules import Issue, ValidationRule
7
+ from .rules.type_hints import TypeHintRule
8
+
9
+
10
+ class ValidationChecker:
11
+ """
12
+ Checks code against all validation rules.
13
+
14
+ Example:
15
+ >>> checker = ValidationChecker()
16
+ >>> issues = checker.check_directory(Path('apps/'))
17
+ >>> for issue in issues:
18
+ ... print(issue)
19
+ """
20
+
21
+ def __init__(self, rules: Optional[List[ValidationRule]] = None):
22
+ """
23
+ Initialize checker.
24
+
25
+ Args:
26
+ rules: List of validation rules. If None, uses default rules.
27
+ """
28
+ if rules is None:
29
+ # Default rules
30
+ self.rules = [
31
+ TypeHintRule(),
32
+ # Future: ResponseSchemaRule(),
33
+ # Future: EnumConflictRule(),
34
+ ]
35
+ else:
36
+ self.rules = rules
37
+
38
+ def check_file(self, file_path: Path) -> List[Issue]:
39
+ """
40
+ Check single file against all rules.
41
+
42
+ Args:
43
+ file_path: Path to Python file
44
+
45
+ Returns:
46
+ List of issues found
47
+ """
48
+ all_issues = []
49
+
50
+ for rule in self.rules:
51
+ try:
52
+ issues = rule.check(file_path)
53
+ all_issues.extend(issues)
54
+ except Exception as e:
55
+ print(f"Error in rule {rule.rule_id} for {file_path}: {e}")
56
+
57
+ return all_issues
58
+
59
+ def check_directory(
60
+ self,
61
+ directory: Path,
62
+ pattern: str = "*serializers.py",
63
+ recursive: bool = True
64
+ ) -> List[Issue]:
65
+ """
66
+ Check all matching files in directory.
67
+
68
+ Args:
69
+ directory: Directory to search
70
+ pattern: File pattern to match (default: *serializers.py)
71
+ recursive: If True, search recursively
72
+
73
+ Returns:
74
+ List of all issues found
75
+ """
76
+ all_issues = []
77
+
78
+ if recursive:
79
+ files = directory.rglob(pattern)
80
+ else:
81
+ files = directory.glob(pattern)
82
+
83
+ for file_path in files:
84
+ if file_path.is_file():
85
+ issues = self.check_file(file_path)
86
+ all_issues.extend(issues)
87
+
88
+ return all_issues
89
+
90
+ def check_files(self, file_paths: List[Path]) -> List[Issue]:
91
+ """
92
+ Check specific list of files.
93
+
94
+ Args:
95
+ file_paths: List of file paths to check
96
+
97
+ Returns:
98
+ List of all issues found
99
+ """
100
+ all_issues = []
101
+
102
+ for file_path in file_paths:
103
+ if file_path.is_file():
104
+ issues = self.check_file(file_path)
105
+ all_issues.extend(issues)
106
+
107
+ return all_issues
108
+
109
+ def get_fixable_issues(self, issues: List[Issue]) -> List[Issue]:
110
+ """
111
+ Filter issues to only auto-fixable ones.
112
+
113
+ Args:
114
+ issues: List of issues
115
+
116
+ Returns:
117
+ List of auto-fixable issues
118
+ """
119
+ fixable = []
120
+
121
+ for issue in issues:
122
+ # Find rule for this issue
123
+ rule = self._get_rule(issue.rule_id)
124
+ if rule and rule.can_fix(issue):
125
+ fixable.append(issue)
126
+
127
+ return fixable
128
+
129
+ def _get_rule(self, rule_id: str) -> Optional[ValidationRule]:
130
+ """Get rule by ID."""
131
+ for rule in self.rules:
132
+ if rule.rule_id == rule_id:
133
+ return rule
134
+ return None
@@ -0,0 +1,216 @@
1
+ """Safe auto-fixer with rollback support."""
2
+
3
+ from pathlib import Path
4
+ from typing import Dict, List, Any
5
+
6
+ from .rules import Issue
7
+ from .rules.type_hints import TypeHintRule
8
+ from .safety import SafetyManager
9
+
10
+
11
+ class SafeFixer:
12
+ """
13
+ Safely apply fixes to code with backup/rollback.
14
+
15
+ Example:
16
+ >>> safety = SafetyManager(workspace=Path('.'))
17
+ >>> fixer = SafeFixer(safety)
18
+ >>> results = fixer.fix_issues(issues, dry_run=False)
19
+ >>> print(f"Fixed: {results['fixed']}")
20
+ """
21
+
22
+ def __init__(self, safety_manager: SafetyManager):
23
+ """
24
+ Initialize fixer.
25
+
26
+ Args:
27
+ safety_manager: SafetyManager instance for backups/rollbacks
28
+ """
29
+ self.safety = safety_manager
30
+ self.rules = {
31
+ 'type-hint-001': TypeHintRule(),
32
+ # Future rules here
33
+ }
34
+
35
+ def fix_issues(
36
+ self,
37
+ issues: List[Issue],
38
+ dry_run: bool = True,
39
+ confirm: bool = True,
40
+ verbose: bool = False
41
+ ) -> Dict[str, Any]:
42
+ """
43
+ Fix issues with safety guarantees.
44
+
45
+ Args:
46
+ issues: List of issues to fix
47
+ dry_run: If True, only show what would be fixed
48
+ confirm: If True, ask for user confirmation
49
+ verbose: If True, show detailed progress
50
+
51
+ Returns:
52
+ Dict with results: {
53
+ 'fixed': int,
54
+ 'failed': int,
55
+ 'skipped': int,
56
+ 'errors': List[str]
57
+ }
58
+ """
59
+ results = {
60
+ 'fixed': 0,
61
+ 'failed': 0,
62
+ 'skipped': 0,
63
+ 'errors': []
64
+ }
65
+
66
+ # Filter auto-fixable issues
67
+ fixable_issues = [i for i in issues if self._can_fix(i)]
68
+
69
+ if not fixable_issues:
70
+ if verbose:
71
+ print("ℹ️ No auto-fixable issues found")
72
+ return results
73
+
74
+ # Dry run: just show what would be fixed
75
+ if dry_run:
76
+ print(f"\n🔍 Dry run: {len(fixable_issues)} issue(s) can be fixed\n")
77
+ for issue in fixable_issues:
78
+ print(f" {issue.file.name}:{issue.line}")
79
+ print(f" {issue.message}")
80
+ print(f" Fix: {issue.suggestion}\n")
81
+ results['skipped'] = len(fixable_issues)
82
+ return results
83
+
84
+ # Ask for confirmation
85
+ if confirm:
86
+ if not self._confirm_fixes(fixable_issues):
87
+ results['skipped'] = len(fixable_issues)
88
+ return results
89
+
90
+ # Start transaction
91
+ transaction_id = self.safety.start_transaction()
92
+ print(f"\n🔧 Starting fixes (transaction: {transaction_id})\n")
93
+
94
+ try:
95
+ # Group by file
96
+ by_file = {}
97
+ for issue in fixable_issues:
98
+ by_file.setdefault(issue.file, []).append(issue)
99
+
100
+ # Fix each file
101
+ for file_path, file_issues in by_file.items():
102
+ if verbose:
103
+ print(f"📝 Fixing {file_path.name} ({len(file_issues)} issue(s))...")
104
+
105
+ # Backup
106
+ self.safety.backup_file(file_path)
107
+
108
+ # Group issues by rule_id for batch processing
109
+ issues_by_rule = {}
110
+ for issue in file_issues:
111
+ issues_by_rule.setdefault(issue.rule_id, []).append(issue)
112
+
113
+ # Apply fixes by rule
114
+ file_success = 0
115
+ for rule_id, rule_issues in issues_by_rule.items():
116
+ rule = self.rules.get(rule_id)
117
+ if not rule:
118
+ results['failed'] += len(rule_issues)
119
+ continue
120
+
121
+ # Use batch fix if available (for better handling of imports)
122
+ if hasattr(rule, 'fix_batch') and len(rule_issues) > 1:
123
+ try:
124
+ if rule.fix_batch(rule_issues):
125
+ file_success += len(rule_issues)
126
+ if verbose:
127
+ for issue in rule_issues:
128
+ print(f" ✓ {issue.message}")
129
+ else:
130
+ results['failed'] += len(rule_issues)
131
+ except Exception as e:
132
+ if verbose:
133
+ print(f" ✗ Batch fix failed: {e}")
134
+ results['failed'] += len(rule_issues)
135
+ else:
136
+ # Fall back to individual fixes
137
+ for issue in rule_issues:
138
+ if self._fix_issue(issue, verbose):
139
+ file_success += 1
140
+ else:
141
+ results['failed'] += 1
142
+
143
+ # Validate syntax
144
+ if file_success > 0:
145
+ if self.safety.validate_syntax(file_path):
146
+ results['fixed'] += file_success
147
+ if verbose:
148
+ print(f" ✅ Fixed {file_success} issue(s)")
149
+ else:
150
+ # Rollback this file
151
+ self.safety.rollback_file(file_path)
152
+ results['failed'] += file_success
153
+ results['errors'].append(f"Syntax error after fixing {file_path.name}")
154
+ print(f" ❌ Syntax error - rolled back {file_path.name}")
155
+
156
+ # Commit if successful
157
+ if results['failed'] == 0:
158
+ self.safety.commit_transaction()
159
+ print(f"\n✅ Successfully fixed {results['fixed']} issue(s)")
160
+ else:
161
+ self.safety.rollback_transaction()
162
+ print(f"\n⚠️ Rolled back due to {results['failed']} failure(s)")
163
+
164
+ except Exception as e:
165
+ # Rollback on any error
166
+ self.safety.rollback_transaction()
167
+ results['errors'].append(str(e))
168
+ print(f"\n❌ Error: {e}")
169
+ print(" Rolled back all changes")
170
+
171
+ return results
172
+
173
+ def _can_fix(self, issue: Issue) -> bool:
174
+ """Check if issue can be auto-fixed."""
175
+ rule = self.rules.get(issue.rule_id)
176
+ return rule and rule.can_fix(issue)
177
+
178
+ def _fix_issue(self, issue: Issue, verbose: bool = False) -> bool:
179
+ """Fix a single issue."""
180
+ rule = self.rules.get(issue.rule_id)
181
+ if not rule:
182
+ return False
183
+
184
+ try:
185
+ success = rule.fix(issue)
186
+ if verbose and success:
187
+ print(f" ✓ {issue.message}")
188
+ return success
189
+ except Exception as e:
190
+ if verbose:
191
+ print(f" ✗ {issue.message}: {e}")
192
+ return False
193
+
194
+ def _confirm_fixes(self, issues: List[Issue]) -> bool:
195
+ """Ask user to confirm fixes."""
196
+ print(f"\n🔧 Ready to fix {len(issues)} issue(s):\n")
197
+
198
+ # Group by file
199
+ by_file = {}
200
+ for issue in issues:
201
+ by_file.setdefault(issue.file, []).append(issue)
202
+
203
+ # Show summary
204
+ for file_path, file_issues in list(by_file.items())[:5]:
205
+ print(f" 📝 {file_path.name}: {len(file_issues)} issue(s)")
206
+ for issue in file_issues[:2]:
207
+ print(f" - {issue.message}")
208
+ if len(file_issues) > 2:
209
+ print(f" ... and {len(file_issues) - 2} more")
210
+
211
+ if len(by_file) > 5:
212
+ print(f" ... and {len(by_file) - 5} more file(s)")
213
+
214
+ print()
215
+ response = input("Apply these fixes? [y/N]: ")
216
+ return response.lower() in ['y', 'yes']