tapps-agents 3.6.0__py3-none-any.whl → 3.6.1__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.
- tapps_agents/__init__.py +2 -2
- tapps_agents/agents/__init__.py +22 -22
- tapps_agents/agents/analyst/__init__.py +5 -5
- tapps_agents/agents/architect/__init__.py +5 -5
- tapps_agents/agents/architect/agent.py +1033 -1033
- tapps_agents/agents/architect/pattern_detector.py +75 -75
- tapps_agents/agents/cleanup/__init__.py +7 -7
- tapps_agents/agents/cleanup/agent.py +445 -445
- tapps_agents/agents/debugger/__init__.py +7 -7
- tapps_agents/agents/debugger/agent.py +310 -310
- tapps_agents/agents/debugger/error_analyzer.py +437 -437
- tapps_agents/agents/designer/__init__.py +5 -5
- tapps_agents/agents/designer/agent.py +786 -786
- tapps_agents/agents/designer/visual_designer.py +638 -638
- tapps_agents/agents/documenter/__init__.py +7 -7
- tapps_agents/agents/documenter/agent.py +531 -531
- tapps_agents/agents/documenter/doc_generator.py +472 -472
- tapps_agents/agents/documenter/doc_validator.py +393 -393
- tapps_agents/agents/documenter/framework_doc_updater.py +493 -493
- tapps_agents/agents/enhancer/__init__.py +7 -7
- tapps_agents/agents/evaluator/__init__.py +7 -7
- tapps_agents/agents/evaluator/agent.py +443 -443
- tapps_agents/agents/evaluator/priority_evaluator.py +641 -641
- tapps_agents/agents/evaluator/quality_analyzer.py +147 -147
- tapps_agents/agents/evaluator/report_generator.py +344 -344
- tapps_agents/agents/evaluator/usage_analyzer.py +192 -192
- tapps_agents/agents/evaluator/workflow_analyzer.py +189 -189
- tapps_agents/agents/implementer/__init__.py +7 -7
- tapps_agents/agents/implementer/agent.py +798 -798
- tapps_agents/agents/implementer/auto_fix.py +1119 -1119
- tapps_agents/agents/implementer/code_generator.py +73 -73
- tapps_agents/agents/improver/__init__.py +1 -1
- tapps_agents/agents/improver/agent.py +753 -753
- tapps_agents/agents/ops/__init__.py +1 -1
- tapps_agents/agents/ops/agent.py +619 -619
- tapps_agents/agents/ops/dependency_analyzer.py +600 -600
- tapps_agents/agents/orchestrator/__init__.py +5 -5
- tapps_agents/agents/orchestrator/agent.py +522 -522
- tapps_agents/agents/planner/__init__.py +7 -7
- tapps_agents/agents/planner/agent.py +1127 -1127
- tapps_agents/agents/reviewer/__init__.py +24 -24
- tapps_agents/agents/reviewer/agent.py +3513 -3513
- tapps_agents/agents/reviewer/aggregator.py +213 -213
- tapps_agents/agents/reviewer/batch_review.py +448 -448
- tapps_agents/agents/reviewer/cache.py +443 -443
- tapps_agents/agents/reviewer/context7_enhancer.py +630 -630
- tapps_agents/agents/reviewer/context_detector.py +203 -203
- tapps_agents/agents/reviewer/docker_compose_validator.py +158 -158
- tapps_agents/agents/reviewer/dockerfile_validator.py +176 -176
- tapps_agents/agents/reviewer/error_handling.py +126 -126
- tapps_agents/agents/reviewer/feedback_generator.py +490 -490
- tapps_agents/agents/reviewer/influxdb_validator.py +316 -316
- tapps_agents/agents/reviewer/issue_tracking.py +169 -169
- tapps_agents/agents/reviewer/library_detector.py +295 -295
- tapps_agents/agents/reviewer/library_patterns.py +268 -268
- tapps_agents/agents/reviewer/maintainability_scorer.py +593 -593
- tapps_agents/agents/reviewer/metric_strategies.py +276 -276
- tapps_agents/agents/reviewer/mqtt_validator.py +160 -160
- tapps_agents/agents/reviewer/output_enhancer.py +105 -105
- tapps_agents/agents/reviewer/pattern_detector.py +241 -241
- tapps_agents/agents/reviewer/performance_scorer.py +357 -357
- tapps_agents/agents/reviewer/phased_review.py +516 -516
- tapps_agents/agents/reviewer/progressive_review.py +435 -435
- tapps_agents/agents/reviewer/react_scorer.py +331 -331
- tapps_agents/agents/reviewer/score_constants.py +228 -228
- tapps_agents/agents/reviewer/score_validator.py +507 -507
- tapps_agents/agents/reviewer/scorer_registry.py +373 -373
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/parallel_executor.py +581 -581
- tapps_agents/agents/reviewer/tools/ruff_grouping.py +250 -250
- tapps_agents/agents/reviewer/tools/scoped_mypy.py +284 -284
- tapps_agents/agents/reviewer/typescript_scorer.py +1142 -1142
- tapps_agents/agents/reviewer/validation.py +208 -208
- tapps_agents/agents/reviewer/websocket_validator.py +132 -132
- tapps_agents/agents/tester/__init__.py +7 -7
- tapps_agents/agents/tester/accessibility_auditor.py +309 -309
- tapps_agents/agents/tester/agent.py +1080 -1080
- tapps_agents/agents/tester/batch_generator.py +54 -54
- tapps_agents/agents/tester/context_learner.py +51 -51
- tapps_agents/agents/tester/coverage_analyzer.py +386 -386
- tapps_agents/agents/tester/coverage_test_generator.py +290 -290
- tapps_agents/agents/tester/debug_enhancer.py +238 -238
- tapps_agents/agents/tester/device_emulator.py +241 -241
- tapps_agents/agents/tester/integration_generator.py +62 -62
- tapps_agents/agents/tester/network_recorder.py +300 -300
- tapps_agents/agents/tester/performance_monitor.py +320 -320
- tapps_agents/agents/tester/test_fixer.py +316 -316
- tapps_agents/agents/tester/test_generator.py +632 -632
- tapps_agents/agents/tester/trace_manager.py +234 -234
- tapps_agents/agents/tester/visual_regression.py +291 -291
- tapps_agents/analysis/pattern_detector.py +36 -36
- tapps_agents/beads/hydration.py +213 -213
- tapps_agents/beads/parse.py +32 -32
- tapps_agents/beads/specs.py +206 -206
- tapps_agents/cli/__init__.py +9 -9
- tapps_agents/cli/__main__.py +8 -8
- tapps_agents/cli/base.py +478 -478
- tapps_agents/cli/command_classifier.py +72 -72
- tapps_agents/cli/commands/__init__.py +2 -2
- tapps_agents/cli/commands/analyst.py +173 -173
- tapps_agents/cli/commands/architect.py +109 -109
- tapps_agents/cli/commands/cleanup_agent.py +92 -92
- tapps_agents/cli/commands/common.py +126 -126
- tapps_agents/cli/commands/debugger.py +90 -90
- tapps_agents/cli/commands/designer.py +112 -112
- tapps_agents/cli/commands/documenter.py +136 -136
- tapps_agents/cli/commands/enhancer.py +110 -110
- tapps_agents/cli/commands/evaluator.py +255 -255
- tapps_agents/cli/commands/implementer.py +301 -301
- tapps_agents/cli/commands/improver.py +91 -91
- tapps_agents/cli/commands/knowledge.py +111 -111
- tapps_agents/cli/commands/learning.py +172 -172
- tapps_agents/cli/commands/observability.py +283 -283
- tapps_agents/cli/commands/ops.py +135 -135
- tapps_agents/cli/commands/orchestrator.py +116 -116
- tapps_agents/cli/commands/planner.py +237 -237
- tapps_agents/cli/commands/reviewer.py +1872 -1872
- tapps_agents/cli/commands/status.py +285 -285
- tapps_agents/cli/commands/task.py +227 -227
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/feedback.py +936 -936
- tapps_agents/cli/formatters.py +608 -608
- tapps_agents/cli/help/__init__.py +7 -7
- tapps_agents/cli/help/static_help.py +425 -425
- tapps_agents/cli/network_detection.py +110 -110
- tapps_agents/cli/output_compactor.py +274 -274
- tapps_agents/cli/parsers/__init__.py +2 -2
- tapps_agents/cli/parsers/analyst.py +186 -186
- tapps_agents/cli/parsers/architect.py +167 -167
- tapps_agents/cli/parsers/cleanup_agent.py +228 -228
- tapps_agents/cli/parsers/debugger.py +116 -116
- tapps_agents/cli/parsers/designer.py +182 -182
- tapps_agents/cli/parsers/documenter.py +134 -134
- tapps_agents/cli/parsers/enhancer.py +113 -113
- tapps_agents/cli/parsers/evaluator.py +213 -213
- tapps_agents/cli/parsers/implementer.py +168 -168
- tapps_agents/cli/parsers/improver.py +132 -132
- tapps_agents/cli/parsers/ops.py +159 -159
- tapps_agents/cli/parsers/orchestrator.py +98 -98
- tapps_agents/cli/parsers/planner.py +145 -145
- tapps_agents/cli/parsers/reviewer.py +462 -462
- tapps_agents/cli/parsers/tester.py +124 -124
- tapps_agents/cli/progress_heartbeat.py +254 -254
- tapps_agents/cli/streaming_progress.py +336 -336
- tapps_agents/cli/utils/__init__.py +6 -6
- tapps_agents/cli/utils/agent_lifecycle.py +48 -48
- tapps_agents/cli/utils/error_formatter.py +82 -82
- tapps_agents/cli/utils/error_recovery.py +188 -188
- tapps_agents/cli/utils/output_handler.py +59 -59
- tapps_agents/cli/utils/prompt_enhancer.py +319 -319
- tapps_agents/cli/validators/__init__.py +9 -9
- tapps_agents/cli/validators/command_validator.py +81 -81
- tapps_agents/context7/__init__.py +112 -112
- tapps_agents/context7/agent_integration.py +869 -869
- tapps_agents/context7/analytics.py +382 -382
- tapps_agents/context7/analytics_dashboard.py +299 -299
- tapps_agents/context7/async_cache.py +681 -681
- tapps_agents/context7/backup_client.py +958 -958
- tapps_agents/context7/cache_locking.py +194 -194
- tapps_agents/context7/cache_metadata.py +214 -214
- tapps_agents/context7/cache_prewarm.py +488 -488
- tapps_agents/context7/cache_structure.py +168 -168
- tapps_agents/context7/cache_warming.py +604 -604
- tapps_agents/context7/circuit_breaker.py +376 -376
- tapps_agents/context7/cleanup.py +461 -461
- tapps_agents/context7/commands.py +858 -858
- tapps_agents/context7/credential_validation.py +276 -276
- tapps_agents/context7/cross_reference_resolver.py +168 -168
- tapps_agents/context7/cross_references.py +424 -424
- tapps_agents/context7/doc_manager.py +225 -225
- tapps_agents/context7/fuzzy_matcher.py +369 -369
- tapps_agents/context7/kb_cache.py +404 -404
- tapps_agents/context7/language_detector.py +219 -219
- tapps_agents/context7/library_detector.py +725 -725
- tapps_agents/context7/lookup.py +738 -738
- tapps_agents/context7/metadata.py +258 -258
- tapps_agents/context7/refresh_queue.py +300 -300
- tapps_agents/context7/security.py +373 -373
- tapps_agents/context7/staleness_policies.py +278 -278
- tapps_agents/context7/tiles_integration.py +47 -47
- tapps_agents/continuous_bug_fix/__init__.py +20 -20
- tapps_agents/continuous_bug_fix/bug_finder.py +306 -306
- tapps_agents/continuous_bug_fix/bug_fix_coordinator.py +177 -177
- tapps_agents/continuous_bug_fix/commit_manager.py +178 -178
- tapps_agents/continuous_bug_fix/continuous_bug_fixer.py +322 -322
- tapps_agents/continuous_bug_fix/proactive_bug_finder.py +285 -285
- tapps_agents/core/__init__.py +298 -298
- tapps_agents/core/adaptive_cache_config.py +432 -432
- tapps_agents/core/agent_base.py +647 -647
- tapps_agents/core/agent_cache.py +466 -466
- tapps_agents/core/agent_learning.py +1865 -1865
- tapps_agents/core/analytics_dashboard.py +563 -563
- tapps_agents/core/analytics_enhancements.py +597 -597
- tapps_agents/core/anonymization.py +274 -274
- tapps_agents/core/ast_parser.py +228 -228
- tapps_agents/core/async_file_ops.py +402 -402
- tapps_agents/core/best_practice_consultant.py +299 -299
- tapps_agents/core/brownfield_analyzer.py +299 -299
- tapps_agents/core/brownfield_review.py +541 -541
- tapps_agents/core/browser_controller.py +513 -513
- tapps_agents/core/capability_registry.py +418 -418
- tapps_agents/core/change_impact_analyzer.py +190 -190
- tapps_agents/core/checkpoint_manager.py +377 -377
- tapps_agents/core/code_generator.py +329 -329
- tapps_agents/core/code_validator.py +276 -276
- tapps_agents/core/command_registry.py +327 -327
- tapps_agents/core/context_gathering/__init__.py +2 -2
- tapps_agents/core/context_gathering/repository_explorer.py +28 -28
- tapps_agents/core/context_intelligence/__init__.py +2 -2
- tapps_agents/core/context_intelligence/relevance_scorer.py +24 -24
- tapps_agents/core/context_intelligence/token_budget_manager.py +27 -27
- tapps_agents/core/context_manager.py +240 -240
- tapps_agents/core/cursor_feedback_monitor.py +146 -146
- tapps_agents/core/cursor_verification.py +290 -290
- tapps_agents/core/customization_loader.py +280 -280
- tapps_agents/core/customization_schema.py +260 -260
- tapps_agents/core/customization_template.py +238 -238
- tapps_agents/core/debug_logger.py +124 -124
- tapps_agents/core/design_validator.py +298 -298
- tapps_agents/core/diagram_generator.py +226 -226
- tapps_agents/core/docker_utils.py +232 -232
- tapps_agents/core/document_generator.py +617 -617
- tapps_agents/core/domain_detector.py +30 -30
- tapps_agents/core/error_envelope.py +454 -454
- tapps_agents/core/error_handler.py +270 -270
- tapps_agents/core/estimation_tracker.py +189 -189
- tapps_agents/core/eval_prompt_engine.py +116 -116
- tapps_agents/core/evaluation_base.py +119 -119
- tapps_agents/core/evaluation_models.py +320 -320
- tapps_agents/core/evaluation_orchestrator.py +225 -225
- tapps_agents/core/evaluators/__init__.py +7 -7
- tapps_agents/core/evaluators/architectural_evaluator.py +205 -205
- tapps_agents/core/evaluators/behavioral_evaluator.py +160 -160
- tapps_agents/core/evaluators/performance_profile_evaluator.py +160 -160
- tapps_agents/core/evaluators/security_posture_evaluator.py +148 -148
- tapps_agents/core/evaluators/spec_compliance_evaluator.py +181 -181
- tapps_agents/core/exceptions.py +107 -107
- tapps_agents/core/expert_config_generator.py +293 -293
- tapps_agents/core/export_schema.py +202 -202
- tapps_agents/core/external_feedback_models.py +102 -102
- tapps_agents/core/external_feedback_storage.py +213 -213
- tapps_agents/core/fallback_strategy.py +314 -314
- tapps_agents/core/feedback_analyzer.py +162 -162
- tapps_agents/core/feedback_collector.py +178 -178
- tapps_agents/core/git_operations.py +445 -445
- tapps_agents/core/hardware_profiler.py +151 -151
- tapps_agents/core/instructions.py +324 -324
- tapps_agents/core/io_guardrails.py +69 -69
- tapps_agents/core/issue_manifest.py +249 -249
- tapps_agents/core/issue_schema.py +139 -139
- tapps_agents/core/json_utils.py +128 -128
- tapps_agents/core/knowledge_graph.py +446 -446
- tapps_agents/core/language_detector.py +296 -296
- tapps_agents/core/learning_confidence.py +242 -242
- tapps_agents/core/learning_dashboard.py +246 -246
- tapps_agents/core/learning_decision.py +384 -384
- tapps_agents/core/learning_explainability.py +578 -578
- tapps_agents/core/learning_export.py +287 -287
- tapps_agents/core/learning_integration.py +228 -228
- tapps_agents/core/llm_behavior.py +232 -232
- tapps_agents/core/long_duration_support.py +786 -786
- tapps_agents/core/mcp_setup.py +106 -106
- tapps_agents/core/memory_integration.py +396 -396
- tapps_agents/core/meta_learning.py +666 -666
- tapps_agents/core/module_path_sanitizer.py +199 -199
- tapps_agents/core/multi_agent_orchestrator.py +382 -382
- tapps_agents/core/network_errors.py +125 -125
- tapps_agents/core/nfr_validator.py +336 -336
- tapps_agents/core/offline_mode.py +158 -158
- tapps_agents/core/output_contracts.py +300 -300
- tapps_agents/core/output_formatter.py +300 -300
- tapps_agents/core/path_normalizer.py +174 -174
- tapps_agents/core/path_validator.py +322 -322
- tapps_agents/core/pattern_library.py +250 -250
- tapps_agents/core/performance_benchmark.py +301 -301
- tapps_agents/core/performance_monitor.py +184 -184
- tapps_agents/core/playwright_mcp_controller.py +771 -771
- tapps_agents/core/policy_loader.py +135 -135
- tapps_agents/core/progress.py +166 -166
- tapps_agents/core/project_profile.py +354 -354
- tapps_agents/core/project_type_detector.py +454 -454
- tapps_agents/core/prompt_base.py +223 -223
- tapps_agents/core/prompt_learning/__init__.py +2 -2
- tapps_agents/core/prompt_learning/learning_loop.py +24 -24
- tapps_agents/core/prompt_learning/project_prompt_store.py +25 -25
- tapps_agents/core/prompt_learning/skills_prompt_analyzer.py +35 -35
- tapps_agents/core/prompt_optimization/__init__.py +6 -6
- tapps_agents/core/prompt_optimization/ab_tester.py +114 -114
- tapps_agents/core/prompt_optimization/correlation_analyzer.py +160 -160
- tapps_agents/core/prompt_optimization/progressive_refiner.py +129 -129
- tapps_agents/core/prompt_optimization/prompt_library.py +37 -37
- tapps_agents/core/requirements_evaluator.py +431 -431
- tapps_agents/core/resource_aware_executor.py +449 -449
- tapps_agents/core/resource_monitor.py +343 -343
- tapps_agents/core/resume_handler.py +298 -298
- tapps_agents/core/retry_handler.py +197 -197
- tapps_agents/core/review_checklists.py +479 -479
- tapps_agents/core/role_loader.py +201 -201
- tapps_agents/core/role_template_loader.py +201 -201
- tapps_agents/core/runtime_mode.py +60 -60
- tapps_agents/core/security_scanner.py +342 -342
- tapps_agents/core/skill_agent_registry.py +194 -194
- tapps_agents/core/skill_integration.py +208 -208
- tapps_agents/core/skill_loader.py +492 -492
- tapps_agents/core/skill_template.py +341 -341
- tapps_agents/core/skill_validator.py +478 -478
- tapps_agents/core/stack_analyzer.py +35 -35
- tapps_agents/core/startup.py +174 -174
- tapps_agents/core/storage_manager.py +397 -397
- tapps_agents/core/storage_models.py +166 -166
- tapps_agents/core/story_evaluator.py +410 -410
- tapps_agents/core/subprocess_utils.py +170 -170
- tapps_agents/core/task_duration.py +296 -296
- tapps_agents/core/task_memory.py +582 -582
- tapps_agents/core/task_state.py +226 -226
- tapps_agents/core/tech_stack_priorities.py +208 -208
- tapps_agents/core/temp_directory.py +194 -194
- tapps_agents/core/template_merger.py +600 -600
- tapps_agents/core/template_selector.py +280 -280
- tapps_agents/core/test_generator.py +286 -286
- tapps_agents/core/tiered_context.py +253 -253
- tapps_agents/core/token_monitor.py +345 -345
- tapps_agents/core/traceability.py +254 -254
- tapps_agents/core/trajectory_tracker.py +50 -50
- tapps_agents/core/unicode_safe.py +143 -143
- tapps_agents/core/unified_cache_config.py +170 -170
- tapps_agents/core/unified_state.py +324 -324
- tapps_agents/core/validate_cursor_setup.py +237 -237
- tapps_agents/core/validation_registry.py +136 -136
- tapps_agents/core/validators/__init__.py +4 -4
- tapps_agents/core/validators/python_validator.py +87 -87
- tapps_agents/core/verification_agent.py +90 -90
- tapps_agents/core/visual_feedback.py +644 -644
- tapps_agents/core/workflow_validator.py +197 -197
- tapps_agents/core/worktree.py +367 -367
- tapps_agents/docker/__init__.py +10 -10
- tapps_agents/docker/analyzer.py +186 -186
- tapps_agents/docker/debugger.py +229 -229
- tapps_agents/docker/error_patterns.py +216 -216
- tapps_agents/epic/__init__.py +22 -22
- tapps_agents/epic/beads_sync.py +115 -115
- tapps_agents/epic/markdown_sync.py +105 -105
- tapps_agents/epic/models.py +96 -96
- tapps_agents/experts/__init__.py +163 -163
- tapps_agents/experts/agent_integration.py +243 -243
- tapps_agents/experts/auto_generator.py +331 -331
- tapps_agents/experts/base_expert.py +536 -536
- tapps_agents/experts/builtin_registry.py +261 -261
- tapps_agents/experts/business_metrics.py +565 -565
- tapps_agents/experts/cache.py +266 -266
- tapps_agents/experts/confidence_breakdown.py +306 -306
- tapps_agents/experts/confidence_calculator.py +336 -336
- tapps_agents/experts/confidence_metrics.py +236 -236
- tapps_agents/experts/domain_config.py +311 -311
- tapps_agents/experts/domain_detector.py +550 -550
- tapps_agents/experts/domain_utils.py +84 -84
- tapps_agents/experts/expert_config.py +113 -113
- tapps_agents/experts/expert_engine.py +465 -465
- tapps_agents/experts/expert_registry.py +744 -744
- tapps_agents/experts/expert_synthesizer.py +70 -70
- tapps_agents/experts/governance.py +197 -197
- tapps_agents/experts/history_logger.py +312 -312
- tapps_agents/experts/knowledge/README.md +180 -180
- tapps_agents/experts/knowledge/accessibility/accessible-forms.md +331 -331
- tapps_agents/experts/knowledge/accessibility/aria-patterns.md +344 -344
- tapps_agents/experts/knowledge/accessibility/color-contrast.md +285 -285
- tapps_agents/experts/knowledge/accessibility/keyboard-navigation.md +332 -332
- tapps_agents/experts/knowledge/accessibility/screen-readers.md +282 -282
- tapps_agents/experts/knowledge/accessibility/semantic-html.md +355 -355
- tapps_agents/experts/knowledge/accessibility/testing-accessibility.md +369 -369
- tapps_agents/experts/knowledge/accessibility/wcag-2.1.md +296 -296
- tapps_agents/experts/knowledge/accessibility/wcag-2.2.md +211 -211
- tapps_agents/experts/knowledge/agent-learning/best-practices.md +715 -715
- tapps_agents/experts/knowledge/agent-learning/pattern-extraction.md +282 -282
- tapps_agents/experts/knowledge/agent-learning/prompt-optimization.md +320 -320
- tapps_agents/experts/knowledge/ai-frameworks/model-optimization.md +90 -90
- tapps_agents/experts/knowledge/ai-frameworks/openvino-patterns.md +260 -260
- tapps_agents/experts/knowledge/api-design-integration/api-gateway-patterns.md +309 -309
- tapps_agents/experts/knowledge/api-design-integration/api-security-patterns.md +521 -521
- tapps_agents/experts/knowledge/api-design-integration/api-versioning.md +421 -421
- tapps_agents/experts/knowledge/api-design-integration/async-protocol-patterns.md +61 -61
- tapps_agents/experts/knowledge/api-design-integration/contract-testing.md +221 -221
- tapps_agents/experts/knowledge/api-design-integration/external-api-integration.md +489 -489
- tapps_agents/experts/knowledge/api-design-integration/fastapi-patterns.md +360 -360
- tapps_agents/experts/knowledge/api-design-integration/fastapi-testing.md +262 -262
- tapps_agents/experts/knowledge/api-design-integration/graphql-patterns.md +582 -582
- tapps_agents/experts/knowledge/api-design-integration/grpc-best-practices.md +499 -499
- tapps_agents/experts/knowledge/api-design-integration/mqtt-patterns.md +455 -455
- tapps_agents/experts/knowledge/api-design-integration/rate-limiting.md +507 -507
- tapps_agents/experts/knowledge/api-design-integration/restful-api-design.md +618 -618
- tapps_agents/experts/knowledge/api-design-integration/websocket-patterns.md +480 -480
- tapps_agents/experts/knowledge/cloud-infrastructure/cloud-native-patterns.md +175 -175
- tapps_agents/experts/knowledge/cloud-infrastructure/container-health-checks.md +261 -261
- tapps_agents/experts/knowledge/cloud-infrastructure/containerization.md +222 -222
- tapps_agents/experts/knowledge/cloud-infrastructure/cost-optimization.md +122 -122
- tapps_agents/experts/knowledge/cloud-infrastructure/disaster-recovery.md +153 -153
- tapps_agents/experts/knowledge/cloud-infrastructure/dockerfile-patterns.md +285 -285
- tapps_agents/experts/knowledge/cloud-infrastructure/infrastructure-as-code.md +187 -187
- tapps_agents/experts/knowledge/cloud-infrastructure/kubernetes-patterns.md +253 -253
- tapps_agents/experts/knowledge/cloud-infrastructure/multi-cloud-strategies.md +155 -155
- tapps_agents/experts/knowledge/cloud-infrastructure/serverless-architecture.md +200 -200
- tapps_agents/experts/knowledge/code-quality-analysis/README.md +16 -16
- tapps_agents/experts/knowledge/code-quality-analysis/code-metrics.md +137 -137
- tapps_agents/experts/knowledge/code-quality-analysis/complexity-analysis.md +181 -181
- tapps_agents/experts/knowledge/code-quality-analysis/technical-debt-patterns.md +191 -191
- tapps_agents/experts/knowledge/data-privacy-compliance/anonymization.md +313 -313
- tapps_agents/experts/knowledge/data-privacy-compliance/ccpa.md +255 -255
- tapps_agents/experts/knowledge/data-privacy-compliance/consent-management.md +282 -282
- tapps_agents/experts/knowledge/data-privacy-compliance/data-minimization.md +275 -275
- tapps_agents/experts/knowledge/data-privacy-compliance/data-retention.md +297 -297
- tapps_agents/experts/knowledge/data-privacy-compliance/data-subject-rights.md +383 -383
- tapps_agents/experts/knowledge/data-privacy-compliance/encryption-privacy.md +285 -285
- tapps_agents/experts/knowledge/data-privacy-compliance/gdpr.md +344 -344
- tapps_agents/experts/knowledge/data-privacy-compliance/hipaa.md +385 -385
- tapps_agents/experts/knowledge/data-privacy-compliance/privacy-by-design.md +280 -280
- tapps_agents/experts/knowledge/database-data-management/acid-vs-cap.md +164 -164
- tapps_agents/experts/knowledge/database-data-management/backup-and-recovery.md +182 -182
- tapps_agents/experts/knowledge/database-data-management/data-modeling.md +172 -172
- tapps_agents/experts/knowledge/database-data-management/database-design.md +187 -187
- tapps_agents/experts/knowledge/database-data-management/flux-query-optimization.md +342 -342
- tapps_agents/experts/knowledge/database-data-management/influxdb-connection-patterns.md +432 -432
- tapps_agents/experts/knowledge/database-data-management/influxdb-patterns.md +442 -442
- tapps_agents/experts/knowledge/database-data-management/migration-strategies.md +216 -216
- tapps_agents/experts/knowledge/database-data-management/nosql-patterns.md +259 -259
- tapps_agents/experts/knowledge/database-data-management/scalability-patterns.md +184 -184
- tapps_agents/experts/knowledge/database-data-management/sql-optimization.md +175 -175
- tapps_agents/experts/knowledge/database-data-management/time-series-modeling.md +444 -444
- tapps_agents/experts/knowledge/development-workflow/README.md +16 -16
- tapps_agents/experts/knowledge/development-workflow/automation-best-practices.md +216 -216
- tapps_agents/experts/knowledge/development-workflow/build-strategies.md +198 -198
- tapps_agents/experts/knowledge/development-workflow/deployment-patterns.md +205 -205
- tapps_agents/experts/knowledge/development-workflow/git-workflows.md +205 -205
- tapps_agents/experts/knowledge/documentation-knowledge-management/README.md +16 -16
- tapps_agents/experts/knowledge/documentation-knowledge-management/api-documentation-patterns.md +231 -231
- tapps_agents/experts/knowledge/documentation-knowledge-management/documentation-standards.md +191 -191
- tapps_agents/experts/knowledge/documentation-knowledge-management/knowledge-management.md +171 -171
- tapps_agents/experts/knowledge/documentation-knowledge-management/technical-writing-guide.md +192 -192
- tapps_agents/experts/knowledge/observability-monitoring/alerting-patterns.md +461 -461
- tapps_agents/experts/knowledge/observability-monitoring/apm-tools.md +459 -459
- tapps_agents/experts/knowledge/observability-monitoring/distributed-tracing.md +367 -367
- tapps_agents/experts/knowledge/observability-monitoring/logging-strategies.md +478 -478
- tapps_agents/experts/knowledge/observability-monitoring/metrics-and-monitoring.md +510 -510
- tapps_agents/experts/knowledge/observability-monitoring/observability-best-practices.md +492 -492
- tapps_agents/experts/knowledge/observability-monitoring/open-telemetry.md +573 -573
- tapps_agents/experts/knowledge/observability-monitoring/slo-sli-sla.md +419 -419
- tapps_agents/experts/knowledge/performance/anti-patterns.md +284 -284
- tapps_agents/experts/knowledge/performance/api-performance.md +256 -256
- tapps_agents/experts/knowledge/performance/caching.md +327 -327
- tapps_agents/experts/knowledge/performance/database-performance.md +252 -252
- tapps_agents/experts/knowledge/performance/optimization-patterns.md +327 -327
- tapps_agents/experts/knowledge/performance/profiling.md +297 -297
- tapps_agents/experts/knowledge/performance/resource-management.md +293 -293
- tapps_agents/experts/knowledge/performance/scalability.md +306 -306
- tapps_agents/experts/knowledge/security/owasp-top10.md +209 -209
- tapps_agents/experts/knowledge/security/secure-coding-practices.md +207 -207
- tapps_agents/experts/knowledge/security/threat-modeling.md +220 -220
- tapps_agents/experts/knowledge/security/vulnerability-patterns.md +342 -342
- tapps_agents/experts/knowledge/software-architecture/docker-compose-patterns.md +314 -314
- tapps_agents/experts/knowledge/software-architecture/microservices-patterns.md +379 -379
- tapps_agents/experts/knowledge/software-architecture/service-communication.md +316 -316
- tapps_agents/experts/knowledge/testing/best-practices.md +310 -310
- tapps_agents/experts/knowledge/testing/coverage-analysis.md +293 -293
- tapps_agents/experts/knowledge/testing/mocking.md +256 -256
- tapps_agents/experts/knowledge/testing/test-automation.md +276 -276
- tapps_agents/experts/knowledge/testing/test-data.md +271 -271
- tapps_agents/experts/knowledge/testing/test-design-patterns.md +280 -280
- tapps_agents/experts/knowledge/testing/test-maintenance.md +236 -236
- tapps_agents/experts/knowledge/testing/test-strategies.md +311 -311
- tapps_agents/experts/knowledge/user-experience/information-architecture.md +325 -325
- tapps_agents/experts/knowledge/user-experience/interaction-design.md +363 -363
- tapps_agents/experts/knowledge/user-experience/prototyping.md +293 -293
- tapps_agents/experts/knowledge/user-experience/usability-heuristics.md +337 -337
- tapps_agents/experts/knowledge/user-experience/usability-testing.md +311 -311
- tapps_agents/experts/knowledge/user-experience/user-journeys.md +296 -296
- tapps_agents/experts/knowledge/user-experience/user-research.md +373 -373
- tapps_agents/experts/knowledge/user-experience/ux-principles.md +340 -340
- tapps_agents/experts/knowledge_freshness.py +321 -321
- tapps_agents/experts/knowledge_ingestion.py +438 -438
- tapps_agents/experts/knowledge_need_detector.py +93 -93
- tapps_agents/experts/knowledge_validator.py +382 -382
- tapps_agents/experts/observability.py +440 -440
- tapps_agents/experts/passive_notifier.py +238 -238
- tapps_agents/experts/proactive_orchestrator.py +32 -32
- tapps_agents/experts/rag_chunker.py +205 -205
- tapps_agents/experts/rag_embedder.py +152 -152
- tapps_agents/experts/rag_evaluation.py +299 -299
- tapps_agents/experts/rag_index.py +303 -303
- tapps_agents/experts/rag_metrics.py +293 -293
- tapps_agents/experts/rag_safety.py +263 -263
- tapps_agents/experts/report_generator.py +296 -296
- tapps_agents/experts/setup_wizard.py +441 -441
- tapps_agents/experts/simple_rag.py +431 -431
- tapps_agents/experts/vector_rag.py +354 -354
- tapps_agents/experts/weight_distributor.py +304 -304
- tapps_agents/health/__init__.py +24 -24
- tapps_agents/health/base.py +75 -75
- tapps_agents/health/checks/__init__.py +22 -22
- tapps_agents/health/checks/automation.py +127 -127
- tapps_agents/health/checks/context7_cache.py +210 -210
- tapps_agents/health/checks/environment.py +116 -116
- tapps_agents/health/checks/execution.py +170 -170
- tapps_agents/health/checks/knowledge_base.py +187 -187
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +324 -0
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +324 -0
- tapps_agents/health/checks/outcomes.py +324 -324
- tapps_agents/health/collector.py +280 -280
- tapps_agents/health/dashboard.py +137 -137
- tapps_agents/health/metrics.py +151 -151
- tapps_agents/health/registry.py +166 -166
- tapps_agents/hooks/__init__.py +33 -33
- tapps_agents/hooks/config.py +140 -140
- tapps_agents/hooks/events.py +135 -135
- tapps_agents/hooks/executor.py +128 -128
- tapps_agents/hooks/manager.py +143 -143
- tapps_agents/integration/__init__.py +8 -8
- tapps_agents/integration/service_integrator.py +121 -121
- tapps_agents/integrations/__init__.py +10 -10
- tapps_agents/integrations/clawdbot.py +525 -525
- tapps_agents/integrations/memory_bridge.py +356 -356
- tapps_agents/mcp/__init__.py +18 -18
- tapps_agents/mcp/gateway.py +112 -112
- tapps_agents/mcp/servers/__init__.py +13 -13
- tapps_agents/mcp/servers/analysis.py +204 -204
- tapps_agents/mcp/servers/context7.py +198 -198
- tapps_agents/mcp/servers/filesystem.py +218 -218
- tapps_agents/mcp/servers/git.py +201 -201
- tapps_agents/mcp/tool_registry.py +115 -115
- tapps_agents/quality/__init__.py +54 -54
- tapps_agents/quality/coverage_analyzer.py +379 -379
- tapps_agents/quality/enforcement.py +82 -82
- tapps_agents/quality/gates/__init__.py +37 -37
- tapps_agents/quality/gates/approval_gate.py +255 -255
- tapps_agents/quality/gates/base.py +84 -84
- tapps_agents/quality/gates/exceptions.py +43 -43
- tapps_agents/quality/gates/policy_gate.py +195 -195
- tapps_agents/quality/gates/registry.py +239 -239
- tapps_agents/quality/gates/security_gate.py +156 -156
- tapps_agents/quality/quality_gates.py +369 -369
- tapps_agents/quality/secret_scanner.py +335 -335
- tapps_agents/resources/__init__.py +5 -0
- tapps_agents/resources/claude/__init__.py +1 -0
- tapps_agents/resources/claude/commands/README.md +156 -0
- tapps_agents/resources/claude/commands/__init__.py +1 -0
- tapps_agents/resources/claude/commands/build-fix.md +22 -0
- tapps_agents/resources/claude/commands/build.md +77 -0
- tapps_agents/resources/claude/commands/debug.md +53 -0
- tapps_agents/resources/claude/commands/design.md +68 -0
- tapps_agents/resources/claude/commands/docs.md +53 -0
- tapps_agents/resources/claude/commands/e2e.md +22 -0
- tapps_agents/resources/claude/commands/fix.md +54 -0
- tapps_agents/resources/claude/commands/implement.md +53 -0
- tapps_agents/resources/claude/commands/improve.md +53 -0
- tapps_agents/resources/claude/commands/library-docs.md +64 -0
- tapps_agents/resources/claude/commands/lint.md +52 -0
- tapps_agents/resources/claude/commands/plan.md +65 -0
- tapps_agents/resources/claude/commands/refactor-clean.md +21 -0
- tapps_agents/resources/claude/commands/refactor.md +55 -0
- tapps_agents/resources/claude/commands/review.md +67 -0
- tapps_agents/resources/claude/commands/score.md +60 -0
- tapps_agents/resources/claude/commands/security-review.md +22 -0
- tapps_agents/resources/claude/commands/security-scan.md +54 -0
- tapps_agents/resources/claude/commands/tdd.md +24 -0
- tapps_agents/resources/claude/commands/test-coverage.md +21 -0
- tapps_agents/resources/claude/commands/test.md +54 -0
- tapps_agents/resources/claude/commands/update-codemaps.md +20 -0
- tapps_agents/resources/claude/commands/update-docs.md +21 -0
- tapps_agents/resources/claude/skills/__init__.py +1 -0
- tapps_agents/resources/claude/skills/analyst/SKILL.md +272 -0
- tapps_agents/resources/claude/skills/analyst/__init__.py +1 -0
- tapps_agents/resources/claude/skills/architect/SKILL.md +282 -0
- tapps_agents/resources/claude/skills/architect/__init__.py +1 -0
- tapps_agents/resources/claude/skills/backend-patterns/SKILL.md +30 -0
- tapps_agents/resources/claude/skills/backend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/coding-standards/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/coding-standards/__init__.py +1 -0
- tapps_agents/resources/claude/skills/debugger/SKILL.md +203 -0
- tapps_agents/resources/claude/skills/debugger/__init__.py +1 -0
- tapps_agents/resources/claude/skills/designer/SKILL.md +243 -0
- tapps_agents/resources/claude/skills/designer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/documenter/SKILL.md +252 -0
- tapps_agents/resources/claude/skills/documenter/__init__.py +1 -0
- tapps_agents/resources/claude/skills/enhancer/SKILL.md +307 -0
- tapps_agents/resources/claude/skills/enhancer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/evaluator/SKILL.md +204 -0
- tapps_agents/resources/claude/skills/evaluator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/frontend-patterns/SKILL.md +29 -0
- tapps_agents/resources/claude/skills/frontend-patterns/__init__.py +1 -0
- tapps_agents/resources/claude/skills/implementer/SKILL.md +188 -0
- tapps_agents/resources/claude/skills/implementer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/improver/SKILL.md +218 -0
- tapps_agents/resources/claude/skills/improver/__init__.py +1 -0
- tapps_agents/resources/claude/skills/ops/SKILL.md +281 -0
- tapps_agents/resources/claude/skills/ops/__init__.py +1 -0
- tapps_agents/resources/claude/skills/orchestrator/SKILL.md +390 -0
- tapps_agents/resources/claude/skills/orchestrator/__init__.py +1 -0
- tapps_agents/resources/claude/skills/planner/SKILL.md +254 -0
- tapps_agents/resources/claude/skills/planner/__init__.py +1 -0
- tapps_agents/resources/claude/skills/reviewer/SKILL.md +434 -0
- tapps_agents/resources/claude/skills/reviewer/__init__.py +1 -0
- tapps_agents/resources/claude/skills/security-review/SKILL.md +31 -0
- tapps_agents/resources/claude/skills/security-review/__init__.py +1 -0
- tapps_agents/resources/claude/skills/simple-mode/SKILL.md +695 -0
- tapps_agents/resources/claude/skills/simple-mode/__init__.py +1 -0
- tapps_agents/resources/claude/skills/tester/SKILL.md +219 -0
- tapps_agents/resources/claude/skills/tester/__init__.py +1 -0
- tapps_agents/resources/cursor/.cursorignore +35 -0
- tapps_agents/resources/cursor/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/__init__.py +1 -0
- tapps_agents/resources/cursor/commands/build-fix.md +11 -0
- tapps_agents/resources/cursor/commands/build.md +11 -0
- tapps_agents/resources/cursor/commands/e2e.md +11 -0
- tapps_agents/resources/cursor/commands/fix.md +11 -0
- tapps_agents/resources/cursor/commands/refactor-clean.md +11 -0
- tapps_agents/resources/cursor/commands/review.md +11 -0
- tapps_agents/resources/cursor/commands/security-review.md +11 -0
- tapps_agents/resources/cursor/commands/tdd.md +11 -0
- tapps_agents/resources/cursor/commands/test-coverage.md +11 -0
- tapps_agents/resources/cursor/commands/test.md +11 -0
- tapps_agents/resources/cursor/commands/update-codemaps.md +10 -0
- tapps_agents/resources/cursor/commands/update-docs.md +11 -0
- tapps_agents/resources/cursor/rules/__init__.py +1 -0
- tapps_agents/resources/cursor/rules/agent-capabilities.mdc +687 -0
- tapps_agents/resources/cursor/rules/coding-style.mdc +31 -0
- tapps_agents/resources/cursor/rules/command-reference.mdc +2081 -0
- tapps_agents/resources/cursor/rules/cursor-mode-usage.mdc +125 -0
- tapps_agents/resources/cursor/rules/git-workflow.mdc +29 -0
- tapps_agents/resources/cursor/rules/performance.mdc +29 -0
- tapps_agents/resources/cursor/rules/project-context.mdc +163 -0
- tapps_agents/resources/cursor/rules/project-profiling.mdc +197 -0
- tapps_agents/resources/cursor/rules/quick-reference.mdc +630 -0
- tapps_agents/resources/cursor/rules/security.mdc +32 -0
- tapps_agents/resources/cursor/rules/simple-mode.mdc +500 -0
- tapps_agents/resources/cursor/rules/testing.mdc +31 -0
- tapps_agents/resources/cursor/rules/when-to-use.mdc +156 -0
- tapps_agents/resources/cursor/rules/workflow-presets.mdc +179 -0
- tapps_agents/resources/customizations/__init__.py +1 -0
- tapps_agents/resources/customizations/example-custom.yaml +83 -0
- tapps_agents/resources/hooks/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/README.md +5 -0
- tapps_agents/resources/hooks/templates/__init__.py +1 -0
- tapps_agents/resources/hooks/templates/add-project-context.yaml +8 -0
- tapps_agents/resources/hooks/templates/auto-format-js.yaml +10 -0
- tapps_agents/resources/hooks/templates/auto-format-python.yaml +10 -0
- tapps_agents/resources/hooks/templates/git-commit-check.yaml +7 -0
- tapps_agents/resources/hooks/templates/notify-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/quality-gate.yaml +8 -0
- tapps_agents/resources/hooks/templates/security-scan-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/session-end-log.yaml +7 -0
- tapps_agents/resources/hooks/templates/show-beads-ready.yaml +8 -0
- tapps_agents/resources/hooks/templates/test-on-edit.yaml +10 -0
- tapps_agents/resources/hooks/templates/update-docs-on-complete.yaml +8 -0
- tapps_agents/resources/hooks/templates/user-prompt-log.yaml +7 -0
- tapps_agents/resources/scripts/__init__.py +1 -0
- tapps_agents/resources/scripts/set_bd_path.ps1 +51 -0
- tapps_agents/resources/workflows/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/__init__.py +1 -0
- tapps_agents/resources/workflows/presets/brownfield-analysis.yaml +235 -0
- tapps_agents/resources/workflows/presets/fix.yaml +78 -0
- tapps_agents/resources/workflows/presets/full-sdlc.yaml +122 -0
- tapps_agents/resources/workflows/presets/quality.yaml +82 -0
- tapps_agents/resources/workflows/presets/rapid-dev.yaml +84 -0
- tapps_agents/session/__init__.py +19 -19
- tapps_agents/session/manager.py +256 -256
- tapps_agents/simple_mode/__init__.py +66 -66
- tapps_agents/simple_mode/agent_contracts.py +357 -357
- tapps_agents/simple_mode/beads_hooks.py +151 -151
- tapps_agents/simple_mode/code_snippet_handler.py +382 -382
- tapps_agents/simple_mode/documentation_manager.py +395 -395
- tapps_agents/simple_mode/documentation_reader.py +187 -187
- tapps_agents/simple_mode/file_inference.py +292 -292
- tapps_agents/simple_mode/framework_change_detector.py +268 -268
- tapps_agents/simple_mode/intent_parser.py +510 -510
- tapps_agents/simple_mode/learning_progression.py +358 -358
- tapps_agents/simple_mode/nl_handler.py +700 -700
- tapps_agents/simple_mode/onboarding.py +253 -253
- tapps_agents/simple_mode/orchestrators/__init__.py +38 -38
- tapps_agents/simple_mode/orchestrators/breakdown_orchestrator.py +49 -49
- tapps_agents/simple_mode/orchestrators/brownfield_orchestrator.py +135 -135
- tapps_agents/simple_mode/orchestrators/deliverable_checklist.py +349 -349
- tapps_agents/simple_mode/orchestrators/enhance_orchestrator.py +53 -53
- tapps_agents/simple_mode/orchestrators/epic_orchestrator.py +122 -122
- tapps_agents/simple_mode/orchestrators/explore_orchestrator.py +184 -184
- tapps_agents/simple_mode/orchestrators/plan_analysis_orchestrator.py +206 -206
- tapps_agents/simple_mode/orchestrators/pr_orchestrator.py +237 -237
- tapps_agents/simple_mode/orchestrators/refactor_orchestrator.py +222 -222
- tapps_agents/simple_mode/orchestrators/requirements_tracer.py +262 -262
- tapps_agents/simple_mode/orchestrators/resume_orchestrator.py +210 -210
- tapps_agents/simple_mode/orchestrators/review_orchestrator.py +161 -161
- tapps_agents/simple_mode/orchestrators/test_orchestrator.py +82 -82
- tapps_agents/simple_mode/output_aggregator.py +340 -340
- tapps_agents/simple_mode/result_formatters.py +598 -598
- tapps_agents/simple_mode/step_dependencies.py +382 -382
- tapps_agents/simple_mode/step_results.py +276 -276
- tapps_agents/simple_mode/streaming.py +388 -388
- tapps_agents/simple_mode/variations.py +129 -129
- tapps_agents/simple_mode/visual_feedback.py +238 -238
- tapps_agents/simple_mode/zero_config.py +274 -274
- tapps_agents/suggestions/__init__.py +8 -8
- tapps_agents/suggestions/inline_suggester.py +52 -52
- tapps_agents/templates/__init__.py +8 -8
- tapps_agents/templates/microservice_generator.py +274 -274
- tapps_agents/utils/env_validator.py +291 -291
- tapps_agents/workflow/__init__.py +171 -171
- tapps_agents/workflow/acceptance_verifier.py +132 -132
- tapps_agents/workflow/agent_handlers/__init__.py +41 -41
- tapps_agents/workflow/agent_handlers/analyst_handler.py +75 -75
- tapps_agents/workflow/agent_handlers/architect_handler.py +107 -107
- tapps_agents/workflow/agent_handlers/base.py +84 -84
- tapps_agents/workflow/agent_handlers/debugger_handler.py +100 -100
- tapps_agents/workflow/agent_handlers/designer_handler.py +110 -110
- tapps_agents/workflow/agent_handlers/documenter_handler.py +94 -94
- tapps_agents/workflow/agent_handlers/implementer_handler.py +235 -235
- tapps_agents/workflow/agent_handlers/ops_handler.py +62 -62
- tapps_agents/workflow/agent_handlers/orchestrator_handler.py +43 -43
- tapps_agents/workflow/agent_handlers/planner_handler.py +98 -98
- tapps_agents/workflow/agent_handlers/registry.py +119 -119
- tapps_agents/workflow/agent_handlers/reviewer_handler.py +119 -119
- tapps_agents/workflow/agent_handlers/tester_handler.py +69 -69
- tapps_agents/workflow/analytics_accessor.py +337 -337
- tapps_agents/workflow/analytics_alerts.py +416 -416
- tapps_agents/workflow/analytics_dashboard_cursor.py +281 -281
- tapps_agents/workflow/analytics_dual_write.py +103 -103
- tapps_agents/workflow/analytics_integration.py +119 -119
- tapps_agents/workflow/analytics_query_parser.py +278 -278
- tapps_agents/workflow/analytics_visualizer.py +259 -259
- tapps_agents/workflow/artifact_helper.py +204 -204
- tapps_agents/workflow/audit_logger.py +263 -263
- tapps_agents/workflow/auto_execution_config.py +340 -340
- tapps_agents/workflow/auto_progression.py +586 -586
- tapps_agents/workflow/branch_cleanup.py +349 -349
- tapps_agents/workflow/checkpoint.py +256 -256
- tapps_agents/workflow/checkpoint_manager.py +178 -178
- tapps_agents/workflow/code_artifact.py +179 -179
- tapps_agents/workflow/common_enums.py +96 -96
- tapps_agents/workflow/confirmation_handler.py +130 -130
- tapps_agents/workflow/context_analyzer.py +222 -222
- tapps_agents/workflow/context_artifact.py +230 -230
- tapps_agents/workflow/cursor_chat.py +94 -94
- tapps_agents/workflow/cursor_skill_helper.py +516 -516
- tapps_agents/workflow/dependency_resolver.py +244 -244
- tapps_agents/workflow/design_artifact.py +156 -156
- tapps_agents/workflow/detector.py +751 -751
- tapps_agents/workflow/direct_execution_fallback.py +301 -301
- tapps_agents/workflow/docs_artifact.py +168 -168
- tapps_agents/workflow/enforcer.py +389 -389
- tapps_agents/workflow/enhancement_artifact.py +142 -142
- tapps_agents/workflow/error_recovery.py +806 -806
- tapps_agents/workflow/event_bus.py +183 -183
- tapps_agents/workflow/event_log.py +612 -612
- tapps_agents/workflow/events.py +63 -63
- tapps_agents/workflow/exceptions.py +43 -43
- tapps_agents/workflow/execution_graph.py +498 -498
- tapps_agents/workflow/execution_plan.py +126 -126
- tapps_agents/workflow/file_utils.py +186 -186
- tapps_agents/workflow/gate_evaluator.py +182 -182
- tapps_agents/workflow/gate_integration.py +200 -200
- tapps_agents/workflow/graph_visualizer.py +130 -130
- tapps_agents/workflow/health_checker.py +206 -206
- tapps_agents/workflow/logging_helper.py +243 -243
- tapps_agents/workflow/manifest.py +582 -582
- tapps_agents/workflow/marker_writer.py +250 -250
- tapps_agents/workflow/messaging.py +325 -325
- tapps_agents/workflow/metadata_models.py +91 -91
- tapps_agents/workflow/metrics_integration.py +226 -226
- tapps_agents/workflow/migration_utils.py +116 -116
- tapps_agents/workflow/models.py +148 -148
- tapps_agents/workflow/nlp_config.py +198 -198
- tapps_agents/workflow/nlp_error_handler.py +207 -207
- tapps_agents/workflow/nlp_executor.py +163 -163
- tapps_agents/workflow/nlp_parser.py +528 -528
- tapps_agents/workflow/observability_dashboard.py +451 -451
- tapps_agents/workflow/observer.py +170 -170
- tapps_agents/workflow/ops_artifact.py +257 -257
- tapps_agents/workflow/output_passing.py +214 -214
- tapps_agents/workflow/parallel_executor.py +463 -463
- tapps_agents/workflow/planning_artifact.py +179 -179
- tapps_agents/workflow/preset_loader.py +285 -285
- tapps_agents/workflow/preset_recommender.py +270 -270
- tapps_agents/workflow/progress_logger.py +145 -145
- tapps_agents/workflow/progress_manager.py +303 -303
- tapps_agents/workflow/progress_monitor.py +186 -186
- tapps_agents/workflow/progress_updates.py +423 -423
- tapps_agents/workflow/quality_artifact.py +158 -158
- tapps_agents/workflow/quality_loopback.py +101 -101
- tapps_agents/workflow/recommender.py +387 -387
- tapps_agents/workflow/remediation_loop.py +166 -166
- tapps_agents/workflow/result_aggregator.py +300 -300
- tapps_agents/workflow/review_artifact.py +185 -185
- tapps_agents/workflow/schema_validator.py +522 -522
- tapps_agents/workflow/session_handoff.py +178 -178
- tapps_agents/workflow/skill_invoker.py +648 -648
- tapps_agents/workflow/state_manager.py +756 -756
- tapps_agents/workflow/state_persistence_config.py +331 -331
- tapps_agents/workflow/status_monitor.py +449 -449
- tapps_agents/workflow/step_checkpoint.py +314 -314
- tapps_agents/workflow/step_details.py +201 -201
- tapps_agents/workflow/story_models.py +147 -147
- tapps_agents/workflow/streaming.py +416 -416
- tapps_agents/workflow/suggestion_engine.py +552 -552
- tapps_agents/workflow/testing_artifact.py +186 -186
- tapps_agents/workflow/timeline.py +158 -158
- tapps_agents/workflow/token_integration.py +209 -209
- tapps_agents/workflow/validation.py +217 -217
- tapps_agents/workflow/visual_feedback.py +391 -391
- tapps_agents/workflow/workflow_chain.py +95 -95
- tapps_agents/workflow/workflow_summary.py +219 -219
- tapps_agents/workflow/worktree_manager.py +724 -724
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/METADATA +672 -672
- tapps_agents-3.6.1.dist-info/RECORD +883 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/licenses/LICENSE +22 -22
- tapps_agents-3.6.0.dist-info/RECORD +0 -758
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/WHEEL +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.6.0.dist-info → tapps_agents-3.6.1.dist-info}/top_level.txt +0 -0
|
@@ -1,1119 +1,1119 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Auto-fix module for ImplementerAgent.
|
|
3
|
-
|
|
4
|
-
Provides automatic code fixing with backup, validation, and rollback capabilities.
|
|
5
|
-
Implements Pipeline pattern (backup → fix → validate → restore) and Decorator pattern
|
|
6
|
-
for safety features.
|
|
7
|
-
|
|
8
|
-
Version: 1.0.0
|
|
9
|
-
Author: TappsCodingAgents
|
|
10
|
-
Date: 2026-01-29
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import ast
|
|
14
|
-
import asyncio
|
|
15
|
-
import hashlib
|
|
16
|
-
import json
|
|
17
|
-
import logging
|
|
18
|
-
import os
|
|
19
|
-
import shutil
|
|
20
|
-
import time
|
|
21
|
-
from dataclasses import dataclass
|
|
22
|
-
from datetime import datetime
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
from typing import Any
|
|
25
|
-
|
|
26
|
-
logger = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ============================================================================
|
|
30
|
-
# Configuration Models
|
|
31
|
-
# ============================================================================
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass(frozen=True)
|
|
35
|
-
class AutoFixConfig:
|
|
36
|
-
"""
|
|
37
|
-
Auto-fix configuration.
|
|
38
|
-
|
|
39
|
-
Attributes:
|
|
40
|
-
enabled: Enable auto-fix
|
|
41
|
-
create_backup: Create backup before fixing
|
|
42
|
-
timeout: Timeout in seconds
|
|
43
|
-
validation_required: Require validation after fixing
|
|
44
|
-
max_backup_age_days: Delete backups older than N days
|
|
45
|
-
backup_location: Backup directory path
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
enabled: bool = True
|
|
49
|
-
create_backup: bool = True
|
|
50
|
-
timeout: int = 30
|
|
51
|
-
validation_required: bool = True
|
|
52
|
-
max_backup_age_days: int = 7
|
|
53
|
-
backup_location: str = ".tapps-agents/backups"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# ============================================================================
|
|
57
|
-
# Data Models
|
|
58
|
-
# ============================================================================
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@dataclass(frozen=True)
|
|
62
|
-
class BackupMetadata:
|
|
63
|
-
"""
|
|
64
|
-
Metadata for file backup operations.
|
|
65
|
-
|
|
66
|
-
Attributes:
|
|
67
|
-
original_path: Path to original file
|
|
68
|
-
backup_path: Path to backup file
|
|
69
|
-
timestamp: When backup was created
|
|
70
|
-
checksum: SHA-256 hash of original file
|
|
71
|
-
size_bytes: Size of original file in bytes
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
original_path: Path
|
|
75
|
-
backup_path: Path
|
|
76
|
-
timestamp: datetime
|
|
77
|
-
checksum: str
|
|
78
|
-
size_bytes: int
|
|
79
|
-
|
|
80
|
-
def to_dict(self) -> dict[str, Any]:
|
|
81
|
-
"""Convert to dictionary for serialization."""
|
|
82
|
-
return {
|
|
83
|
-
"original_path": str(self.original_path),
|
|
84
|
-
"backup_path": str(self.backup_path),
|
|
85
|
-
"timestamp": self.timestamp.isoformat(),
|
|
86
|
-
"checksum": self.checksum,
|
|
87
|
-
"size_bytes": self.size_bytes,
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
@classmethod
|
|
91
|
-
def from_dict(cls, data: dict[str, Any]) -> "BackupMetadata":
|
|
92
|
-
"""Create from dictionary."""
|
|
93
|
-
return cls(
|
|
94
|
-
original_path=Path(data["original_path"]),
|
|
95
|
-
backup_path=Path(data["backup_path"]),
|
|
96
|
-
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
97
|
-
checksum=data["checksum"],
|
|
98
|
-
size_bytes=data["size_bytes"],
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass(frozen=True)
|
|
103
|
-
class ValidationResult:
|
|
104
|
-
"""
|
|
105
|
-
Result of validation checks.
|
|
106
|
-
|
|
107
|
-
Attributes:
|
|
108
|
-
passed: Overall validation passed
|
|
109
|
-
syntax_valid: Syntax validation passed
|
|
110
|
-
imports_valid: Import validation passed
|
|
111
|
-
linting_valid: Linting validation passed
|
|
112
|
-
errors: List of validation errors
|
|
113
|
-
warnings: List of validation warnings
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
passed: bool
|
|
117
|
-
syntax_valid: bool
|
|
118
|
-
imports_valid: bool
|
|
119
|
-
linting_valid: bool
|
|
120
|
-
errors: list[str]
|
|
121
|
-
warnings: list[str]
|
|
122
|
-
|
|
123
|
-
def to_dict(self) -> dict[str, Any]:
|
|
124
|
-
"""Convert to dictionary for serialization."""
|
|
125
|
-
return {
|
|
126
|
-
"passed": self.passed,
|
|
127
|
-
"syntax_valid": self.syntax_valid,
|
|
128
|
-
"imports_valid": self.imports_valid,
|
|
129
|
-
"linting_valid": self.linting_valid,
|
|
130
|
-
"errors": self.errors,
|
|
131
|
-
"warnings": self.warnings,
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@dataclass(frozen=True)
|
|
136
|
-
class AutoFixResult:
|
|
137
|
-
"""
|
|
138
|
-
Result of auto-fix operation.
|
|
139
|
-
|
|
140
|
-
Attributes:
|
|
141
|
-
success: Whether auto-fix succeeded
|
|
142
|
-
fixes_applied: Number of issues fixed
|
|
143
|
-
validation_passed: Whether validation passed after fixing
|
|
144
|
-
backup_created: Whether backup was created
|
|
145
|
-
backup_metadata: Backup metadata if created
|
|
146
|
-
errors: List of error messages
|
|
147
|
-
warnings: List of warning messages
|
|
148
|
-
duration_seconds: Time taken for operation
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
success: bool
|
|
152
|
-
fixes_applied: int
|
|
153
|
-
validation_passed: bool
|
|
154
|
-
backup_created: bool
|
|
155
|
-
backup_metadata: BackupMetadata | None
|
|
156
|
-
errors: list[str]
|
|
157
|
-
warnings: list[str]
|
|
158
|
-
duration_seconds: float
|
|
159
|
-
|
|
160
|
-
def to_dict(self) -> dict[str, Any]:
|
|
161
|
-
"""Convert to dictionary for serialization."""
|
|
162
|
-
return {
|
|
163
|
-
"success": self.success,
|
|
164
|
-
"fixes_applied": self.fixes_applied,
|
|
165
|
-
"validation_passed": self.validation_passed,
|
|
166
|
-
"backup_created": self.backup_created,
|
|
167
|
-
"backup_metadata": self.backup_metadata.to_dict()
|
|
168
|
-
if self.backup_metadata
|
|
169
|
-
else None,
|
|
170
|
-
"errors": self.errors,
|
|
171
|
-
"warnings": self.warnings,
|
|
172
|
-
"duration_seconds": self.duration_seconds,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# ============================================================================
|
|
177
|
-
# Custom Exceptions
|
|
178
|
-
# ============================================================================
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
class AutoFixError(Exception):
|
|
182
|
-
"""Base exception for auto-fix errors."""
|
|
183
|
-
|
|
184
|
-
pass
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class BackupFailedError(AutoFixError):
|
|
188
|
-
"""Backup creation failed."""
|
|
189
|
-
|
|
190
|
-
def __init__(self, file_path: Path, reason: str):
|
|
191
|
-
self.file_path = file_path
|
|
192
|
-
self.reason = reason
|
|
193
|
-
super().__init__(f"Backup failed for {file_path}: {reason}")
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
class ValidationFailedError(AutoFixError):
|
|
197
|
-
"""Validation failed after auto-fix."""
|
|
198
|
-
|
|
199
|
-
def __init__(self, file_path: Path, errors: list[str]):
|
|
200
|
-
self.file_path = file_path
|
|
201
|
-
self.errors = errors
|
|
202
|
-
super().__init__(f"Validation failed for {file_path}: {errors}")
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
class RestoreFailedError(AutoFixError):
|
|
206
|
-
"""Restore from backup failed."""
|
|
207
|
-
|
|
208
|
-
def __init__(self, backup: BackupMetadata, reason: str):
|
|
209
|
-
self.backup = backup
|
|
210
|
-
self.reason = reason
|
|
211
|
-
super().__init__(f"Restore failed for {backup.backup_path}: {reason}")
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
# ============================================================================
|
|
215
|
-
# BackupManager
|
|
216
|
-
# ============================================================================
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class BackupManager:
|
|
220
|
-
"""
|
|
221
|
-
Manage file backups with atomic operations.
|
|
222
|
-
|
|
223
|
-
Features:
|
|
224
|
-
- Timestamped backups
|
|
225
|
-
- SHA-256 checksums
|
|
226
|
-
- Atomic file operations
|
|
227
|
-
- Automatic cleanup of old backups
|
|
228
|
-
|
|
229
|
-
Thread-safe: Yes (atomic operations, no shared state)
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
|
-
def __init__(self, config: AutoFixConfig):
|
|
233
|
-
"""
|
|
234
|
-
Initialize BackupManager.
|
|
235
|
-
|
|
236
|
-
Args:
|
|
237
|
-
config: Auto-fix configuration
|
|
238
|
-
"""
|
|
239
|
-
self.config = config
|
|
240
|
-
self.logger = logging.getLogger(__name__)
|
|
241
|
-
|
|
242
|
-
def create_backup(
|
|
243
|
-
self, file_path: Path, *, backup_dir: Path | None = None
|
|
244
|
-
) -> BackupMetadata:
|
|
245
|
-
"""
|
|
246
|
-
Create timestamped backup with checksum.
|
|
247
|
-
|
|
248
|
-
Backup filename format: {stem}.backup_{timestamp}{suffix}
|
|
249
|
-
Example: module.py.backup_20260129_143022
|
|
250
|
-
|
|
251
|
-
Args:
|
|
252
|
-
file_path: Path to file to backup
|
|
253
|
-
backup_dir: Optional backup directory (default: from config)
|
|
254
|
-
|
|
255
|
-
Returns:
|
|
256
|
-
BackupMetadata with backup information
|
|
257
|
-
|
|
258
|
-
Raises:
|
|
259
|
-
BackupFailedError: If backup creation fails
|
|
260
|
-
FileNotFoundError: If file doesn't exist
|
|
261
|
-
|
|
262
|
-
Security:
|
|
263
|
-
- Backup file has 0o600 permissions (owner read/write only)
|
|
264
|
-
- Atomic file operations (write to temp, then rename)
|
|
265
|
-
- Path validation (no traversal)
|
|
266
|
-
|
|
267
|
-
Performance:
|
|
268
|
-
- Target: <1 second for files <10MB
|
|
269
|
-
- Uses shutil.copy2 (preserves metadata)
|
|
270
|
-
"""
|
|
271
|
-
start_time = time.time()
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
# Validate file exists
|
|
275
|
-
if not file_path.exists():
|
|
276
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
277
|
-
|
|
278
|
-
# Validate path (prevent traversal)
|
|
279
|
-
if not self._is_safe_path(file_path):
|
|
280
|
-
raise BackupFailedError(
|
|
281
|
-
file_path, "Path traversal detected or unsafe path"
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
# Calculate checksum
|
|
285
|
-
checksum = self._calculate_checksum(file_path)
|
|
286
|
-
size_bytes = file_path.stat().st_size
|
|
287
|
-
|
|
288
|
-
# Create backup directory
|
|
289
|
-
if backup_dir is None:
|
|
290
|
-
backup_dir = Path(self.config.backup_location)
|
|
291
|
-
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
292
|
-
|
|
293
|
-
# Generate backup filename
|
|
294
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
295
|
-
backup_filename = f"{file_path.stem}.backup_{timestamp}{file_path.suffix}"
|
|
296
|
-
backup_path = backup_dir / backup_filename
|
|
297
|
-
|
|
298
|
-
# Copy file to backup (atomic operation)
|
|
299
|
-
temp_path = backup_path.with_suffix(".tmp")
|
|
300
|
-
try:
|
|
301
|
-
shutil.copy2(file_path, temp_path)
|
|
302
|
-
# Set restrictive permissions (0o600 = owner read/write only)
|
|
303
|
-
os.chmod(temp_path, 0o600)
|
|
304
|
-
# Atomic rename
|
|
305
|
-
os.replace(temp_path, backup_path)
|
|
306
|
-
finally:
|
|
307
|
-
# Clean up temp file if it exists
|
|
308
|
-
if temp_path.exists():
|
|
309
|
-
temp_path.unlink()
|
|
310
|
-
|
|
311
|
-
metadata = BackupMetadata(
|
|
312
|
-
original_path=file_path,
|
|
313
|
-
backup_path=backup_path,
|
|
314
|
-
timestamp=datetime.now(),
|
|
315
|
-
checksum=checksum,
|
|
316
|
-
size_bytes=size_bytes,
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
duration = time.time() - start_time
|
|
320
|
-
self.logger.info(
|
|
321
|
-
f"Created backup: {backup_path} (checksum: {checksum[:8]}..., "
|
|
322
|
-
f"size: {size_bytes} bytes, duration: {duration:.2f}s)"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
return metadata
|
|
326
|
-
|
|
327
|
-
except FileNotFoundError:
|
|
328
|
-
raise
|
|
329
|
-
except Exception as e:
|
|
330
|
-
raise BackupFailedError(file_path, str(e)) from e
|
|
331
|
-
|
|
332
|
-
def validate_backup(self, backup: BackupMetadata) -> bool:
|
|
333
|
-
"""
|
|
334
|
-
Validate backup integrity using checksum.
|
|
335
|
-
|
|
336
|
-
Args:
|
|
337
|
-
backup: Backup metadata to validate
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
True if backup is valid, False otherwise
|
|
341
|
-
"""
|
|
342
|
-
try:
|
|
343
|
-
if not backup.backup_path.exists():
|
|
344
|
-
self.logger.warning(f"Backup file not found: {backup.backup_path}")
|
|
345
|
-
return False
|
|
346
|
-
|
|
347
|
-
current_checksum = self._calculate_checksum(backup.backup_path)
|
|
348
|
-
is_valid = current_checksum == backup.checksum
|
|
349
|
-
|
|
350
|
-
if not is_valid:
|
|
351
|
-
self.logger.warning(
|
|
352
|
-
f"Backup checksum mismatch: expected {backup.checksum[:8]}..., "
|
|
353
|
-
f"got {current_checksum[:8]}..."
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
return is_valid
|
|
357
|
-
|
|
358
|
-
except Exception as e:
|
|
359
|
-
self.logger.error(f"Error validating backup: {e}")
|
|
360
|
-
return False
|
|
361
|
-
|
|
362
|
-
def cleanup_old_backups(
|
|
363
|
-
self, file_path: Path, keep_count: int | None = None
|
|
364
|
-
) -> int:
|
|
365
|
-
"""
|
|
366
|
-
Clean up old backups, keeping only recent N.
|
|
367
|
-
|
|
368
|
-
Args:
|
|
369
|
-
file_path: Original file path
|
|
370
|
-
keep_count: Number of backups to keep (default: from config)
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
Number of backups deleted
|
|
374
|
-
"""
|
|
375
|
-
if keep_count is None:
|
|
376
|
-
keep_count = self.config.max_backup_age_days # Reuse config value
|
|
377
|
-
|
|
378
|
-
try:
|
|
379
|
-
backups = self.list_backups(file_path)
|
|
380
|
-
|
|
381
|
-
# Keep only N most recent backups
|
|
382
|
-
if len(backups) <= keep_count:
|
|
383
|
-
return 0
|
|
384
|
-
|
|
385
|
-
backups_to_delete = backups[keep_count:]
|
|
386
|
-
deleted_count = 0
|
|
387
|
-
|
|
388
|
-
for backup in backups_to_delete:
|
|
389
|
-
try:
|
|
390
|
-
backup.backup_path.unlink()
|
|
391
|
-
deleted_count += 1
|
|
392
|
-
self.logger.debug(f"Deleted old backup: {backup.backup_path}")
|
|
393
|
-
except Exception as e:
|
|
394
|
-
self.logger.warning(f"Failed to delete backup {backup.backup_path}: {e}")
|
|
395
|
-
|
|
396
|
-
return deleted_count
|
|
397
|
-
|
|
398
|
-
except Exception as e:
|
|
399
|
-
self.logger.error(f"Error cleaning up backups: {e}")
|
|
400
|
-
return 0
|
|
401
|
-
|
|
402
|
-
def list_backups(self, file_path: Path) -> list[BackupMetadata]:
|
|
403
|
-
"""
|
|
404
|
-
List all backups for a file, sorted by timestamp (newest first).
|
|
405
|
-
|
|
406
|
-
Args:
|
|
407
|
-
file_path: Original file path
|
|
408
|
-
|
|
409
|
-
Returns:
|
|
410
|
-
List of backup metadata
|
|
411
|
-
"""
|
|
412
|
-
backup_dir = Path(self.config.backup_location)
|
|
413
|
-
if not backup_dir.exists():
|
|
414
|
-
return []
|
|
415
|
-
|
|
416
|
-
# Find backup files matching pattern
|
|
417
|
-
pattern = f"{file_path.stem}.backup_*{file_path.suffix}"
|
|
418
|
-
backup_files = list(backup_dir.glob(pattern))
|
|
419
|
-
|
|
420
|
-
backups = []
|
|
421
|
-
for backup_file in backup_files:
|
|
422
|
-
try:
|
|
423
|
-
# Extract timestamp from filename
|
|
424
|
-
timestamp_str = backup_file.stem.split(".backup_")[1]
|
|
425
|
-
timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
|
426
|
-
|
|
427
|
-
checksum = self._calculate_checksum(backup_file)
|
|
428
|
-
size_bytes = backup_file.stat().st_size
|
|
429
|
-
|
|
430
|
-
metadata = BackupMetadata(
|
|
431
|
-
original_path=file_path,
|
|
432
|
-
backup_path=backup_file,
|
|
433
|
-
timestamp=timestamp,
|
|
434
|
-
checksum=checksum,
|
|
435
|
-
size_bytes=size_bytes,
|
|
436
|
-
)
|
|
437
|
-
backups.append(metadata)
|
|
438
|
-
|
|
439
|
-
except Exception as e:
|
|
440
|
-
self.logger.warning(f"Failed to parse backup {backup_file}: {e}")
|
|
441
|
-
|
|
442
|
-
# Sort by timestamp (newest first)
|
|
443
|
-
backups.sort(key=lambda b: b.timestamp, reverse=True)
|
|
444
|
-
return backups
|
|
445
|
-
|
|
446
|
-
def _calculate_checksum(self, file_path: Path) -> str:
|
|
447
|
-
"""Calculate SHA-256 checksum of file."""
|
|
448
|
-
sha256 = hashlib.sha256()
|
|
449
|
-
with open(file_path, "rb") as f:
|
|
450
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
451
|
-
sha256.update(chunk)
|
|
452
|
-
return sha256.hexdigest()
|
|
453
|
-
|
|
454
|
-
def _is_safe_path(self, file_path: Path) -> bool:
|
|
455
|
-
"""
|
|
456
|
-
Validate path is safe (no traversal, within project).
|
|
457
|
-
|
|
458
|
-
Security: Prevent path traversal attacks.
|
|
459
|
-
"""
|
|
460
|
-
try:
|
|
461
|
-
# Resolve to absolute path
|
|
462
|
-
abs_path = file_path.resolve()
|
|
463
|
-
|
|
464
|
-
# Check for path traversal (../)
|
|
465
|
-
if ".." in str(file_path):
|
|
466
|
-
return False
|
|
467
|
-
|
|
468
|
-
# Check file exists or parent exists (for new files)
|
|
469
|
-
if not abs_path.exists() and not abs_path.parent.exists():
|
|
470
|
-
return False
|
|
471
|
-
|
|
472
|
-
return True
|
|
473
|
-
|
|
474
|
-
except Exception:
|
|
475
|
-
return False
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
# ============================================================================
|
|
479
|
-
# ValidationManager
|
|
480
|
-
# ============================================================================
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
class ValidationManager:
|
|
484
|
-
"""
|
|
485
|
-
Validate code with multiple checks.
|
|
486
|
-
|
|
487
|
-
Validation types:
|
|
488
|
-
- Syntax validation (AST parsing)
|
|
489
|
-
- Import validation (check imports exist)
|
|
490
|
-
- Linting validation (Ruff clean run)
|
|
491
|
-
|
|
492
|
-
Thread-safe: Yes (no shared state)
|
|
493
|
-
Async: Yes (uses async subprocess)
|
|
494
|
-
"""
|
|
495
|
-
|
|
496
|
-
def __init__(self, config: AutoFixConfig):
|
|
497
|
-
"""
|
|
498
|
-
Initialize ValidationManager.
|
|
499
|
-
|
|
500
|
-
Args:
|
|
501
|
-
config: Auto-fix configuration
|
|
502
|
-
"""
|
|
503
|
-
self.config = config
|
|
504
|
-
self.logger = logging.getLogger(__name__)
|
|
505
|
-
|
|
506
|
-
async def validate_syntax(self, file_path: Path) -> ValidationResult:
|
|
507
|
-
"""
|
|
508
|
-
Validate Python syntax using AST parsing.
|
|
509
|
-
|
|
510
|
-
Args:
|
|
511
|
-
file_path: Path to file to validate
|
|
512
|
-
|
|
513
|
-
Returns:
|
|
514
|
-
ValidationResult with syntax validation status
|
|
515
|
-
|
|
516
|
-
Performance:
|
|
517
|
-
- Target: <100ms for files <1000 lines
|
|
518
|
-
- Uses ast.parse (no subprocess)
|
|
519
|
-
"""
|
|
520
|
-
errors = []
|
|
521
|
-
warnings = []
|
|
522
|
-
|
|
523
|
-
try:
|
|
524
|
-
with open(file_path, encoding="utf-8") as f:
|
|
525
|
-
code = f.read()
|
|
526
|
-
|
|
527
|
-
# Parse AST
|
|
528
|
-
ast.parse(code, filename=str(file_path))
|
|
529
|
-
syntax_valid = True
|
|
530
|
-
|
|
531
|
-
except SyntaxError as e:
|
|
532
|
-
syntax_valid = False
|
|
533
|
-
errors.append(f"Syntax error at line {e.lineno}: {e.msg}")
|
|
534
|
-
self.logger.warning(f"Syntax validation failed: {e}")
|
|
535
|
-
|
|
536
|
-
except Exception as e:
|
|
537
|
-
syntax_valid = False
|
|
538
|
-
errors.append(f"Unexpected error during syntax validation: {e}")
|
|
539
|
-
self.logger.error(f"Syntax validation error: {e}")
|
|
540
|
-
|
|
541
|
-
return ValidationResult(
|
|
542
|
-
passed=syntax_valid,
|
|
543
|
-
syntax_valid=syntax_valid,
|
|
544
|
-
imports_valid=True, # Not checked in this method
|
|
545
|
-
linting_valid=True, # Not checked in this method
|
|
546
|
-
errors=errors,
|
|
547
|
-
warnings=warnings,
|
|
548
|
-
)
|
|
549
|
-
|
|
550
|
-
async def validate_imports(self, file_path: Path) -> ValidationResult:
|
|
551
|
-
"""
|
|
552
|
-
Validate imports are resolvable.
|
|
553
|
-
|
|
554
|
-
Args:
|
|
555
|
-
file_path: Path to file to validate
|
|
556
|
-
|
|
557
|
-
Returns:
|
|
558
|
-
ValidationResult with import validation status
|
|
559
|
-
|
|
560
|
-
Performance:
|
|
561
|
-
- Target: <200ms
|
|
562
|
-
- Parses imports from AST
|
|
563
|
-
- Checks module existence
|
|
564
|
-
"""
|
|
565
|
-
errors = []
|
|
566
|
-
warnings = []
|
|
567
|
-
imports_valid = True
|
|
568
|
-
|
|
569
|
-
try:
|
|
570
|
-
with open(file_path, encoding="utf-8") as f:
|
|
571
|
-
code = f.read()
|
|
572
|
-
|
|
573
|
-
tree = ast.parse(code, filename=str(file_path))
|
|
574
|
-
|
|
575
|
-
# Extract imports
|
|
576
|
-
for node in ast.walk(tree):
|
|
577
|
-
if isinstance(node, ast.Import):
|
|
578
|
-
for alias in node.names:
|
|
579
|
-
if not self._check_import(alias.name):
|
|
580
|
-
warnings.append(f"Cannot resolve import: {alias.name}")
|
|
581
|
-
|
|
582
|
-
elif isinstance(node, ast.ImportFrom):
|
|
583
|
-
if node.module and not self._check_import(node.module):
|
|
584
|
-
warnings.append(f"Cannot resolve import: {node.module}")
|
|
585
|
-
|
|
586
|
-
except Exception as e:
|
|
587
|
-
imports_valid = False
|
|
588
|
-
errors.append(f"Import validation error: {e}")
|
|
589
|
-
self.logger.error(f"Import validation failed: {e}")
|
|
590
|
-
|
|
591
|
-
return ValidationResult(
|
|
592
|
-
passed=imports_valid and not errors,
|
|
593
|
-
syntax_valid=True, # Not checked in this method
|
|
594
|
-
imports_valid=imports_valid,
|
|
595
|
-
linting_valid=True, # Not checked in this method
|
|
596
|
-
errors=errors,
|
|
597
|
-
warnings=warnings,
|
|
598
|
-
)
|
|
599
|
-
|
|
600
|
-
async def validate_linting(self, file_path: Path) -> ValidationResult:
|
|
601
|
-
"""
|
|
602
|
-
Validate linting with Ruff (check for new issues).
|
|
603
|
-
|
|
604
|
-
Args:
|
|
605
|
-
file_path: Path to file to validate
|
|
606
|
-
|
|
607
|
-
Returns:
|
|
608
|
-
ValidationResult with linting validation status
|
|
609
|
-
|
|
610
|
-
Performance:
|
|
611
|
-
- Target: <500ms
|
|
612
|
-
- Uses `ruff check` subprocess
|
|
613
|
-
"""
|
|
614
|
-
errors = []
|
|
615
|
-
warnings = []
|
|
616
|
-
linting_valid = True
|
|
617
|
-
|
|
618
|
-
try:
|
|
619
|
-
# Run Ruff check (no --fix)
|
|
620
|
-
result = await asyncio.create_subprocess_exec(
|
|
621
|
-
"ruff",
|
|
622
|
-
"check",
|
|
623
|
-
"--output-format=json",
|
|
624
|
-
str(file_path),
|
|
625
|
-
stdout=asyncio.subprocess.PIPE,
|
|
626
|
-
stderr=asyncio.subprocess.PIPE,
|
|
627
|
-
)
|
|
628
|
-
|
|
629
|
-
stdout, stderr = await asyncio.wait_for(
|
|
630
|
-
result.communicate(), timeout=self.config.timeout
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
if result.returncode != 0:
|
|
634
|
-
# Parse JSON output
|
|
635
|
-
try:
|
|
636
|
-
issues = json.loads(stdout.decode())
|
|
637
|
-
if issues:
|
|
638
|
-
linting_valid = False
|
|
639
|
-
for issue in issues:
|
|
640
|
-
errors.append(
|
|
641
|
-
f"Line {issue.get('location', {}).get('row', '?')}: "
|
|
642
|
-
f"{issue.get('message', 'Unknown error')} "
|
|
643
|
-
f"[{issue.get('code', 'unknown')}]"
|
|
644
|
-
)
|
|
645
|
-
except json.JSONDecodeError:
|
|
646
|
-
# Ruff output is not JSON, treat as warning
|
|
647
|
-
warnings.append("Could not parse Ruff output")
|
|
648
|
-
|
|
649
|
-
except TimeoutError:
|
|
650
|
-
warnings.append(f"Ruff validation timed out after {self.config.timeout}s")
|
|
651
|
-
except FileNotFoundError:
|
|
652
|
-
warnings.append("Ruff not found (skipping linting validation)")
|
|
653
|
-
except Exception as e:
|
|
654
|
-
warnings.append(f"Linting validation error: {e}")
|
|
655
|
-
self.logger.error(f"Linting validation failed: {e}")
|
|
656
|
-
|
|
657
|
-
return ValidationResult(
|
|
658
|
-
passed=linting_valid and not errors,
|
|
659
|
-
syntax_valid=True, # Not checked in this method
|
|
660
|
-
imports_valid=True, # Not checked in this method
|
|
661
|
-
linting_valid=linting_valid,
|
|
662
|
-
errors=errors,
|
|
663
|
-
warnings=warnings,
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
async def validate_all(self, file_path: Path) -> ValidationResult:
|
|
667
|
-
"""
|
|
668
|
-
Run all validation checks.
|
|
669
|
-
|
|
670
|
-
Args:
|
|
671
|
-
file_path: Path to file to validate
|
|
672
|
-
|
|
673
|
-
Returns:
|
|
674
|
-
ValidationResult with all validation statuses
|
|
675
|
-
|
|
676
|
-
Performance:
|
|
677
|
-
- Target: <1 second
|
|
678
|
-
- Fails fast on first error
|
|
679
|
-
"""
|
|
680
|
-
all_errors = []
|
|
681
|
-
all_warnings = []
|
|
682
|
-
|
|
683
|
-
# Syntax validation (fast, fail fast)
|
|
684
|
-
syntax_result = await self.validate_syntax(file_path)
|
|
685
|
-
all_errors.extend(syntax_result.errors)
|
|
686
|
-
all_warnings.extend(syntax_result.warnings)
|
|
687
|
-
|
|
688
|
-
if not syntax_result.syntax_valid:
|
|
689
|
-
# Fail fast - syntax errors prevent other checks
|
|
690
|
-
return ValidationResult(
|
|
691
|
-
passed=False,
|
|
692
|
-
syntax_valid=False,
|
|
693
|
-
imports_valid=False,
|
|
694
|
-
linting_valid=False,
|
|
695
|
-
errors=all_errors,
|
|
696
|
-
warnings=all_warnings,
|
|
697
|
-
)
|
|
698
|
-
|
|
699
|
-
# Import validation
|
|
700
|
-
import_result = await self.validate_imports(file_path)
|
|
701
|
-
all_errors.extend(import_result.errors)
|
|
702
|
-
all_warnings.extend(import_result.warnings)
|
|
703
|
-
|
|
704
|
-
# Linting validation
|
|
705
|
-
linting_result = await self.validate_linting(file_path)
|
|
706
|
-
all_errors.extend(linting_result.errors)
|
|
707
|
-
all_warnings.extend(linting_result.warnings)
|
|
708
|
-
|
|
709
|
-
passed = (
|
|
710
|
-
syntax_result.syntax_valid
|
|
711
|
-
and import_result.imports_valid
|
|
712
|
-
and linting_result.linting_valid
|
|
713
|
-
and not all_errors
|
|
714
|
-
)
|
|
715
|
-
|
|
716
|
-
return ValidationResult(
|
|
717
|
-
passed=passed,
|
|
718
|
-
syntax_valid=syntax_result.syntax_valid,
|
|
719
|
-
imports_valid=import_result.imports_valid,
|
|
720
|
-
linting_valid=linting_result.linting_valid,
|
|
721
|
-
errors=all_errors,
|
|
722
|
-
warnings=all_warnings,
|
|
723
|
-
)
|
|
724
|
-
|
|
725
|
-
def _check_import(self, module_name: str) -> bool:
|
|
726
|
-
"""
|
|
727
|
-
Check if module can be imported.
|
|
728
|
-
|
|
729
|
-
Returns:
|
|
730
|
-
True if module exists, False otherwise
|
|
731
|
-
"""
|
|
732
|
-
try:
|
|
733
|
-
__import__(module_name.split(".")[0])
|
|
734
|
-
return True
|
|
735
|
-
except ImportError:
|
|
736
|
-
return False
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
# ============================================================================
|
|
740
|
-
# RestoreManager
|
|
741
|
-
# ============================================================================
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
class RestoreManager:
|
|
745
|
-
"""
|
|
746
|
-
Restore files from backups with atomic operations.
|
|
747
|
-
|
|
748
|
-
Features:
|
|
749
|
-
- Atomic restore (write to temp, then rename)
|
|
750
|
-
- Checksum verification
|
|
751
|
-
- Metadata preservation
|
|
752
|
-
|
|
753
|
-
Thread-safe: Yes (atomic operations)
|
|
754
|
-
"""
|
|
755
|
-
|
|
756
|
-
def __init__(self, config: AutoFixConfig):
|
|
757
|
-
"""
|
|
758
|
-
Initialize RestoreManager.
|
|
759
|
-
|
|
760
|
-
Args:
|
|
761
|
-
config: Auto-fix configuration
|
|
762
|
-
"""
|
|
763
|
-
self.config = config
|
|
764
|
-
self.logger = logging.getLogger(__name__)
|
|
765
|
-
|
|
766
|
-
async def restore(self, backup: BackupMetadata) -> None:
|
|
767
|
-
"""
|
|
768
|
-
Restore file from backup with verification.
|
|
769
|
-
|
|
770
|
-
Args:
|
|
771
|
-
backup: Backup metadata
|
|
772
|
-
|
|
773
|
-
Raises:
|
|
774
|
-
RestoreFailedError: If restore fails
|
|
775
|
-
FileNotFoundError: If backup doesn't exist
|
|
776
|
-
|
|
777
|
-
Performance:
|
|
778
|
-
- Target: <1 second
|
|
779
|
-
- Uses atomic rename
|
|
780
|
-
|
|
781
|
-
Security:
|
|
782
|
-
- Validates checksum after restore
|
|
783
|
-
- Uses atomic operations (no partial states)
|
|
784
|
-
"""
|
|
785
|
-
try:
|
|
786
|
-
if not backup.backup_path.exists():
|
|
787
|
-
raise FileNotFoundError(f"Backup not found: {backup.backup_path}")
|
|
788
|
-
|
|
789
|
-
# Verify backup integrity
|
|
790
|
-
current_checksum = self._calculate_checksum(backup.backup_path)
|
|
791
|
-
if current_checksum != backup.checksum:
|
|
792
|
-
raise RestoreFailedError(
|
|
793
|
-
backup, f"Backup checksum mismatch: {current_checksum[:8]}..."
|
|
794
|
-
)
|
|
795
|
-
|
|
796
|
-
# Copy backup to temp location
|
|
797
|
-
temp_path = backup.original_path.with_suffix(".restore_tmp")
|
|
798
|
-
try:
|
|
799
|
-
shutil.copy2(backup.backup_path, temp_path)
|
|
800
|
-
# Atomic rename
|
|
801
|
-
os.replace(temp_path, backup.original_path)
|
|
802
|
-
|
|
803
|
-
self.logger.info(f"Restored file from backup: {backup.backup_path}")
|
|
804
|
-
|
|
805
|
-
finally:
|
|
806
|
-
# Clean up temp file if it exists
|
|
807
|
-
if temp_path.exists():
|
|
808
|
-
temp_path.unlink()
|
|
809
|
-
|
|
810
|
-
# Verify restore
|
|
811
|
-
if not self.verify_restore(backup.original_path, backup):
|
|
812
|
-
raise RestoreFailedError(
|
|
813
|
-
backup, "Restore verification failed (checksum mismatch)"
|
|
814
|
-
)
|
|
815
|
-
|
|
816
|
-
except (FileNotFoundError, RestoreFailedError):
|
|
817
|
-
raise
|
|
818
|
-
except Exception as e:
|
|
819
|
-
raise RestoreFailedError(backup, str(e)) from e
|
|
820
|
-
|
|
821
|
-
def verify_restore(self, file_path: Path, backup: BackupMetadata) -> bool:
|
|
822
|
-
"""
|
|
823
|
-
Verify restored file matches backup checksum.
|
|
824
|
-
|
|
825
|
-
Args:
|
|
826
|
-
file_path: Restored file path
|
|
827
|
-
backup: Original backup metadata
|
|
828
|
-
|
|
829
|
-
Returns:
|
|
830
|
-
True if checksums match, False otherwise
|
|
831
|
-
"""
|
|
832
|
-
try:
|
|
833
|
-
current_checksum = self._calculate_checksum(file_path)
|
|
834
|
-
return current_checksum == backup.checksum
|
|
835
|
-
except Exception as e:
|
|
836
|
-
self.logger.error(f"Restore verification error: {e}")
|
|
837
|
-
return False
|
|
838
|
-
|
|
839
|
-
def _calculate_checksum(self, file_path: Path) -> str:
|
|
840
|
-
"""Calculate SHA-256 checksum of file."""
|
|
841
|
-
sha256 = hashlib.sha256()
|
|
842
|
-
with open(file_path, "rb") as f:
|
|
843
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
844
|
-
sha256.update(chunk)
|
|
845
|
-
return sha256.hexdigest()
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
# ============================================================================
|
|
849
|
-
# AutoFixModule (Main Orchestrator)
|
|
850
|
-
# ============================================================================
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
class AutoFixModule:
|
|
854
|
-
"""
|
|
855
|
-
Auto-fix module for ImplementerAgent.
|
|
856
|
-
|
|
857
|
-
Orchestrates backup → fix → validate → restore workflow.
|
|
858
|
-
Integrates with Ruff for auto-fixes.
|
|
859
|
-
|
|
860
|
-
Thread-safe: Yes (immutable results, no shared state)
|
|
861
|
-
Async: Yes (uses async subprocess)
|
|
862
|
-
"""
|
|
863
|
-
|
|
864
|
-
def __init__(
|
|
865
|
-
self,
|
|
866
|
-
config: AutoFixConfig,
|
|
867
|
-
backup_manager: BackupManager | None = None,
|
|
868
|
-
validation_manager: ValidationManager | None = None,
|
|
869
|
-
restore_manager: RestoreManager | None = None,
|
|
870
|
-
):
|
|
871
|
-
"""
|
|
872
|
-
Initialize AutoFixModule.
|
|
873
|
-
|
|
874
|
-
Args:
|
|
875
|
-
config: Auto-fix configuration
|
|
876
|
-
backup_manager: Optional custom backup manager
|
|
877
|
-
validation_manager: Optional custom validation manager
|
|
878
|
-
restore_manager: Optional custom restore manager
|
|
879
|
-
|
|
880
|
-
Raises:
|
|
881
|
-
ValueError: If config is invalid
|
|
882
|
-
"""
|
|
883
|
-
self.config = config
|
|
884
|
-
self.backup_manager = backup_manager or BackupManager(config)
|
|
885
|
-
self.validation_manager = validation_manager or ValidationManager(config)
|
|
886
|
-
self.restore_manager = restore_manager or RestoreManager(config)
|
|
887
|
-
self.logger = logging.getLogger(__name__)
|
|
888
|
-
|
|
889
|
-
async def auto_fix(
|
|
890
|
-
self,
|
|
891
|
-
file_path: Path,
|
|
892
|
-
*,
|
|
893
|
-
create_backup: bool = True,
|
|
894
|
-
timeout: int | None = None,
|
|
895
|
-
) -> AutoFixResult:
|
|
896
|
-
"""
|
|
897
|
-
Apply auto-fixes to file with validation and rollback.
|
|
898
|
-
|
|
899
|
-
Workflow:
|
|
900
|
-
1. Validate file path (security check)
|
|
901
|
-
2. Create backup (if enabled)
|
|
902
|
-
3. Run `ruff check --fix`
|
|
903
|
-
4. Validate fixes (syntax, imports, linting)
|
|
904
|
-
5. Rollback if validation fails
|
|
905
|
-
6. Return result
|
|
906
|
-
|
|
907
|
-
Args:
|
|
908
|
-
file_path: Path to file to auto-fix
|
|
909
|
-
create_backup: Whether to create backup before fixing (default: True)
|
|
910
|
-
timeout: Optional timeout in seconds (default: from config)
|
|
911
|
-
|
|
912
|
-
Returns:
|
|
913
|
-
AutoFixResult with success status and metadata
|
|
914
|
-
|
|
915
|
-
Raises:
|
|
916
|
-
Never raises - returns failure result instead
|
|
917
|
-
|
|
918
|
-
Performance:
|
|
919
|
-
- Target: <5 seconds for files <1000 lines
|
|
920
|
-
- Backup: <1 second
|
|
921
|
-
- Fix: <3 seconds
|
|
922
|
-
- Validation: <1 second
|
|
923
|
-
|
|
924
|
-
Security:
|
|
925
|
-
- Validates file path is within project
|
|
926
|
-
- Uses subprocess with list args (no shell injection)
|
|
927
|
-
- Creates backup with restrictive permissions (0o600)
|
|
928
|
-
"""
|
|
929
|
-
start_time = time.time()
|
|
930
|
-
errors = []
|
|
931
|
-
warnings = []
|
|
932
|
-
backup_metadata = None
|
|
933
|
-
fixes_applied = 0
|
|
934
|
-
|
|
935
|
-
try:
|
|
936
|
-
# Validate file path
|
|
937
|
-
if not file_path.exists():
|
|
938
|
-
errors.append(f"File not found: {file_path}")
|
|
939
|
-
return self._create_error_result(errors, warnings, start_time)
|
|
940
|
-
|
|
941
|
-
if not self.backup_manager._is_safe_path(file_path):
|
|
942
|
-
errors.append("Unsafe file path (path traversal detected)")
|
|
943
|
-
return self._create_error_result(errors, warnings, start_time)
|
|
944
|
-
|
|
945
|
-
# Step 1: Create backup
|
|
946
|
-
if create_backup and self.config.create_backup:
|
|
947
|
-
try:
|
|
948
|
-
backup_metadata = self.backup_manager.create_backup(file_path)
|
|
949
|
-
self.logger.info(f"Backup created: {backup_metadata.backup_path}")
|
|
950
|
-
except BackupFailedError as e:
|
|
951
|
-
errors.append(f"Backup failed: {e.reason}")
|
|
952
|
-
return self._create_error_result(
|
|
953
|
-
errors, warnings, start_time, backup_metadata=None
|
|
954
|
-
)
|
|
955
|
-
|
|
956
|
-
# Step 2: Run ruff check --fix
|
|
957
|
-
try:
|
|
958
|
-
fix_timeout = timeout or self.config.timeout
|
|
959
|
-
result = await asyncio.create_subprocess_exec(
|
|
960
|
-
"ruff",
|
|
961
|
-
"check",
|
|
962
|
-
"--fix",
|
|
963
|
-
"--output-format=json",
|
|
964
|
-
str(file_path),
|
|
965
|
-
stdout=asyncio.subprocess.PIPE,
|
|
966
|
-
stderr=asyncio.subprocess.PIPE,
|
|
967
|
-
)
|
|
968
|
-
|
|
969
|
-
stdout, stderr = await asyncio.wait_for(
|
|
970
|
-
result.communicate(), timeout=fix_timeout
|
|
971
|
-
)
|
|
972
|
-
|
|
973
|
-
# Count fixes applied from JSON output
|
|
974
|
-
try:
|
|
975
|
-
output = json.loads(stdout.decode())
|
|
976
|
-
fixes_applied = len(
|
|
977
|
-
[issue for issue in output if issue.get("fixed", False)]
|
|
978
|
-
)
|
|
979
|
-
except json.JSONDecodeError:
|
|
980
|
-
# Cannot parse output, assume some fixes applied if returncode == 0
|
|
981
|
-
fixes_applied = 1 if result.returncode == 0 else 0
|
|
982
|
-
|
|
983
|
-
self.logger.info(f"Applied {fixes_applied} auto-fixes")
|
|
984
|
-
|
|
985
|
-
except TimeoutError:
|
|
986
|
-
errors.append(f"Ruff timed out after {fix_timeout}s")
|
|
987
|
-
await self._rollback_if_needed(backup_metadata, errors)
|
|
988
|
-
return self._create_error_result(
|
|
989
|
-
errors, warnings, start_time, backup_metadata
|
|
990
|
-
)
|
|
991
|
-
|
|
992
|
-
except FileNotFoundError:
|
|
993
|
-
warnings.append("Ruff not found (skipping auto-fix)")
|
|
994
|
-
return self._create_error_result(
|
|
995
|
-
errors, warnings, start_time, backup_metadata
|
|
996
|
-
)
|
|
997
|
-
|
|
998
|
-
except Exception as e:
|
|
999
|
-
errors.append(f"Ruff execution failed: {e}")
|
|
1000
|
-
await self._rollback_if_needed(backup_metadata, errors)
|
|
1001
|
-
return self._create_error_result(
|
|
1002
|
-
errors, warnings, start_time, backup_metadata
|
|
1003
|
-
)
|
|
1004
|
-
|
|
1005
|
-
# Step 3: Validate fixes
|
|
1006
|
-
if self.config.validation_required:
|
|
1007
|
-
validation_result = await self.validation_manager.validate_all(file_path)
|
|
1008
|
-
|
|
1009
|
-
if not validation_result.passed:
|
|
1010
|
-
errors.extend(validation_result.errors)
|
|
1011
|
-
warnings.extend(validation_result.warnings)
|
|
1012
|
-
|
|
1013
|
-
# Rollback on validation failure
|
|
1014
|
-
await self._rollback_if_needed(backup_metadata, errors)
|
|
1015
|
-
|
|
1016
|
-
return AutoFixResult(
|
|
1017
|
-
success=False,
|
|
1018
|
-
fixes_applied=fixes_applied,
|
|
1019
|
-
validation_passed=False,
|
|
1020
|
-
backup_created=backup_metadata is not None,
|
|
1021
|
-
backup_metadata=backup_metadata,
|
|
1022
|
-
errors=errors,
|
|
1023
|
-
warnings=warnings,
|
|
1024
|
-
duration_seconds=time.time() - start_time,
|
|
1025
|
-
)
|
|
1026
|
-
|
|
1027
|
-
warnings.extend(validation_result.warnings)
|
|
1028
|
-
|
|
1029
|
-
# Success
|
|
1030
|
-
duration = time.time() - start_time
|
|
1031
|
-
self.logger.info(
|
|
1032
|
-
f"Auto-fix completed successfully: {fixes_applied} fixes in {duration:.2f}s"
|
|
1033
|
-
)
|
|
1034
|
-
|
|
1035
|
-
return AutoFixResult(
|
|
1036
|
-
success=True,
|
|
1037
|
-
fixes_applied=fixes_applied,
|
|
1038
|
-
validation_passed=True,
|
|
1039
|
-
backup_created=backup_metadata is not None,
|
|
1040
|
-
backup_metadata=backup_metadata,
|
|
1041
|
-
errors=[],
|
|
1042
|
-
warnings=warnings,
|
|
1043
|
-
duration_seconds=duration,
|
|
1044
|
-
)
|
|
1045
|
-
|
|
1046
|
-
except Exception as e:
|
|
1047
|
-
# Unexpected error - never raise, return error result
|
|
1048
|
-
errors.append(f"Unexpected error: {e}")
|
|
1049
|
-
self.logger.exception(f"Auto-fix failed with unexpected error: {e}")
|
|
1050
|
-
await self._rollback_if_needed(backup_metadata, errors)
|
|
1051
|
-
return self._create_error_result(errors, warnings, start_time, backup_metadata)
|
|
1052
|
-
|
|
1053
|
-
async def validate_fixes(self, file_path: Path) -> ValidationResult:
|
|
1054
|
-
"""
|
|
1055
|
-
Validate fixes without applying them (dry-run check).
|
|
1056
|
-
|
|
1057
|
-
Args:
|
|
1058
|
-
file_path: Path to file to validate
|
|
1059
|
-
|
|
1060
|
-
Returns:
|
|
1061
|
-
ValidationResult with validation status
|
|
1062
|
-
|
|
1063
|
-
Raises:
|
|
1064
|
-
FileNotFoundError: If file doesn't exist
|
|
1065
|
-
"""
|
|
1066
|
-
if not file_path.exists():
|
|
1067
|
-
raise FileNotFoundError(f"File not found: {file_path}")
|
|
1068
|
-
|
|
1069
|
-
return await self.validation_manager.validate_all(file_path)
|
|
1070
|
-
|
|
1071
|
-
async def rollback(self, backup_metadata: BackupMetadata) -> None:
|
|
1072
|
-
"""
|
|
1073
|
-
Manually rollback to backup.
|
|
1074
|
-
|
|
1075
|
-
Args:
|
|
1076
|
-
backup_metadata: Backup to restore from
|
|
1077
|
-
|
|
1078
|
-
Raises:
|
|
1079
|
-
RestoreFailedError: If restore fails
|
|
1080
|
-
FileNotFoundError: If backup doesn't exist
|
|
1081
|
-
"""
|
|
1082
|
-
await self.restore_manager.restore(backup_metadata)
|
|
1083
|
-
|
|
1084
|
-
async def _rollback_if_needed(
|
|
1085
|
-
self, backup_metadata: BackupMetadata | None, errors: list[str]
|
|
1086
|
-
) -> None:
|
|
1087
|
-
"""
|
|
1088
|
-
Rollback to backup if available.
|
|
1089
|
-
|
|
1090
|
-
Args:
|
|
1091
|
-
backup_metadata: Backup to restore from (None = no rollback)
|
|
1092
|
-
errors: List to append rollback errors to
|
|
1093
|
-
"""
|
|
1094
|
-
if backup_metadata is not None:
|
|
1095
|
-
try:
|
|
1096
|
-
await self.restore_manager.restore(backup_metadata)
|
|
1097
|
-
self.logger.info(f"Rolled back to backup: {backup_metadata.backup_path}")
|
|
1098
|
-
except RestoreFailedError as e:
|
|
1099
|
-
errors.append(f"Rollback failed: {e.reason}")
|
|
1100
|
-
self.logger.error(f"Rollback failed: {e}")
|
|
1101
|
-
|
|
1102
|
-
def _create_error_result(
|
|
1103
|
-
self,
|
|
1104
|
-
errors: list[str],
|
|
1105
|
-
warnings: list[str],
|
|
1106
|
-
start_time: float,
|
|
1107
|
-
backup_metadata: BackupMetadata | None = None,
|
|
1108
|
-
) -> AutoFixResult:
|
|
1109
|
-
"""Create error result with standard fields."""
|
|
1110
|
-
return AutoFixResult(
|
|
1111
|
-
success=False,
|
|
1112
|
-
fixes_applied=0,
|
|
1113
|
-
validation_passed=False,
|
|
1114
|
-
backup_created=backup_metadata is not None,
|
|
1115
|
-
backup_metadata=backup_metadata,
|
|
1116
|
-
errors=errors,
|
|
1117
|
-
warnings=warnings,
|
|
1118
|
-
duration_seconds=time.time() - start_time,
|
|
1119
|
-
)
|
|
1
|
+
"""
|
|
2
|
+
Auto-fix module for ImplementerAgent.
|
|
3
|
+
|
|
4
|
+
Provides automatic code fixing with backup, validation, and rollback capabilities.
|
|
5
|
+
Implements Pipeline pattern (backup → fix → validate → restore) and Decorator pattern
|
|
6
|
+
for safety features.
|
|
7
|
+
|
|
8
|
+
Version: 1.0.0
|
|
9
|
+
Author: TappsCodingAgents
|
|
10
|
+
Date: 2026-01-29
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import ast
|
|
14
|
+
import asyncio
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# Configuration Models
|
|
31
|
+
# ============================================================================
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class AutoFixConfig:
|
|
36
|
+
"""
|
|
37
|
+
Auto-fix configuration.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
enabled: Enable auto-fix
|
|
41
|
+
create_backup: Create backup before fixing
|
|
42
|
+
timeout: Timeout in seconds
|
|
43
|
+
validation_required: Require validation after fixing
|
|
44
|
+
max_backup_age_days: Delete backups older than N days
|
|
45
|
+
backup_location: Backup directory path
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
enabled: bool = True
|
|
49
|
+
create_backup: bool = True
|
|
50
|
+
timeout: int = 30
|
|
51
|
+
validation_required: bool = True
|
|
52
|
+
max_backup_age_days: int = 7
|
|
53
|
+
backup_location: str = ".tapps-agents/backups"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ============================================================================
|
|
57
|
+
# Data Models
|
|
58
|
+
# ============================================================================
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class BackupMetadata:
|
|
63
|
+
"""
|
|
64
|
+
Metadata for file backup operations.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
original_path: Path to original file
|
|
68
|
+
backup_path: Path to backup file
|
|
69
|
+
timestamp: When backup was created
|
|
70
|
+
checksum: SHA-256 hash of original file
|
|
71
|
+
size_bytes: Size of original file in bytes
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
original_path: Path
|
|
75
|
+
backup_path: Path
|
|
76
|
+
timestamp: datetime
|
|
77
|
+
checksum: str
|
|
78
|
+
size_bytes: int
|
|
79
|
+
|
|
80
|
+
def to_dict(self) -> dict[str, Any]:
|
|
81
|
+
"""Convert to dictionary for serialization."""
|
|
82
|
+
return {
|
|
83
|
+
"original_path": str(self.original_path),
|
|
84
|
+
"backup_path": str(self.backup_path),
|
|
85
|
+
"timestamp": self.timestamp.isoformat(),
|
|
86
|
+
"checksum": self.checksum,
|
|
87
|
+
"size_bytes": self.size_bytes,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_dict(cls, data: dict[str, Any]) -> "BackupMetadata":
|
|
92
|
+
"""Create from dictionary."""
|
|
93
|
+
return cls(
|
|
94
|
+
original_path=Path(data["original_path"]),
|
|
95
|
+
backup_path=Path(data["backup_path"]),
|
|
96
|
+
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
97
|
+
checksum=data["checksum"],
|
|
98
|
+
size_bytes=data["size_bytes"],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class ValidationResult:
|
|
104
|
+
"""
|
|
105
|
+
Result of validation checks.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
passed: Overall validation passed
|
|
109
|
+
syntax_valid: Syntax validation passed
|
|
110
|
+
imports_valid: Import validation passed
|
|
111
|
+
linting_valid: Linting validation passed
|
|
112
|
+
errors: List of validation errors
|
|
113
|
+
warnings: List of validation warnings
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
passed: bool
|
|
117
|
+
syntax_valid: bool
|
|
118
|
+
imports_valid: bool
|
|
119
|
+
linting_valid: bool
|
|
120
|
+
errors: list[str]
|
|
121
|
+
warnings: list[str]
|
|
122
|
+
|
|
123
|
+
def to_dict(self) -> dict[str, Any]:
|
|
124
|
+
"""Convert to dictionary for serialization."""
|
|
125
|
+
return {
|
|
126
|
+
"passed": self.passed,
|
|
127
|
+
"syntax_valid": self.syntax_valid,
|
|
128
|
+
"imports_valid": self.imports_valid,
|
|
129
|
+
"linting_valid": self.linting_valid,
|
|
130
|
+
"errors": self.errors,
|
|
131
|
+
"warnings": self.warnings,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class AutoFixResult:
|
|
137
|
+
"""
|
|
138
|
+
Result of auto-fix operation.
|
|
139
|
+
|
|
140
|
+
Attributes:
|
|
141
|
+
success: Whether auto-fix succeeded
|
|
142
|
+
fixes_applied: Number of issues fixed
|
|
143
|
+
validation_passed: Whether validation passed after fixing
|
|
144
|
+
backup_created: Whether backup was created
|
|
145
|
+
backup_metadata: Backup metadata if created
|
|
146
|
+
errors: List of error messages
|
|
147
|
+
warnings: List of warning messages
|
|
148
|
+
duration_seconds: Time taken for operation
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
success: bool
|
|
152
|
+
fixes_applied: int
|
|
153
|
+
validation_passed: bool
|
|
154
|
+
backup_created: bool
|
|
155
|
+
backup_metadata: BackupMetadata | None
|
|
156
|
+
errors: list[str]
|
|
157
|
+
warnings: list[str]
|
|
158
|
+
duration_seconds: float
|
|
159
|
+
|
|
160
|
+
def to_dict(self) -> dict[str, Any]:
|
|
161
|
+
"""Convert to dictionary for serialization."""
|
|
162
|
+
return {
|
|
163
|
+
"success": self.success,
|
|
164
|
+
"fixes_applied": self.fixes_applied,
|
|
165
|
+
"validation_passed": self.validation_passed,
|
|
166
|
+
"backup_created": self.backup_created,
|
|
167
|
+
"backup_metadata": self.backup_metadata.to_dict()
|
|
168
|
+
if self.backup_metadata
|
|
169
|
+
else None,
|
|
170
|
+
"errors": self.errors,
|
|
171
|
+
"warnings": self.warnings,
|
|
172
|
+
"duration_seconds": self.duration_seconds,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ============================================================================
|
|
177
|
+
# Custom Exceptions
|
|
178
|
+
# ============================================================================
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class AutoFixError(Exception):
|
|
182
|
+
"""Base exception for auto-fix errors."""
|
|
183
|
+
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class BackupFailedError(AutoFixError):
|
|
188
|
+
"""Backup creation failed."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, file_path: Path, reason: str):
|
|
191
|
+
self.file_path = file_path
|
|
192
|
+
self.reason = reason
|
|
193
|
+
super().__init__(f"Backup failed for {file_path}: {reason}")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ValidationFailedError(AutoFixError):
|
|
197
|
+
"""Validation failed after auto-fix."""
|
|
198
|
+
|
|
199
|
+
def __init__(self, file_path: Path, errors: list[str]):
|
|
200
|
+
self.file_path = file_path
|
|
201
|
+
self.errors = errors
|
|
202
|
+
super().__init__(f"Validation failed for {file_path}: {errors}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class RestoreFailedError(AutoFixError):
|
|
206
|
+
"""Restore from backup failed."""
|
|
207
|
+
|
|
208
|
+
def __init__(self, backup: BackupMetadata, reason: str):
|
|
209
|
+
self.backup = backup
|
|
210
|
+
self.reason = reason
|
|
211
|
+
super().__init__(f"Restore failed for {backup.backup_path}: {reason}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ============================================================================
|
|
215
|
+
# BackupManager
|
|
216
|
+
# ============================================================================
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class BackupManager:
|
|
220
|
+
"""
|
|
221
|
+
Manage file backups with atomic operations.
|
|
222
|
+
|
|
223
|
+
Features:
|
|
224
|
+
- Timestamped backups
|
|
225
|
+
- SHA-256 checksums
|
|
226
|
+
- Atomic file operations
|
|
227
|
+
- Automatic cleanup of old backups
|
|
228
|
+
|
|
229
|
+
Thread-safe: Yes (atomic operations, no shared state)
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(self, config: AutoFixConfig):
|
|
233
|
+
"""
|
|
234
|
+
Initialize BackupManager.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
config: Auto-fix configuration
|
|
238
|
+
"""
|
|
239
|
+
self.config = config
|
|
240
|
+
self.logger = logging.getLogger(__name__)
|
|
241
|
+
|
|
242
|
+
def create_backup(
|
|
243
|
+
self, file_path: Path, *, backup_dir: Path | None = None
|
|
244
|
+
) -> BackupMetadata:
|
|
245
|
+
"""
|
|
246
|
+
Create timestamped backup with checksum.
|
|
247
|
+
|
|
248
|
+
Backup filename format: {stem}.backup_{timestamp}{suffix}
|
|
249
|
+
Example: module.py.backup_20260129_143022
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
file_path: Path to file to backup
|
|
253
|
+
backup_dir: Optional backup directory (default: from config)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
BackupMetadata with backup information
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
BackupFailedError: If backup creation fails
|
|
260
|
+
FileNotFoundError: If file doesn't exist
|
|
261
|
+
|
|
262
|
+
Security:
|
|
263
|
+
- Backup file has 0o600 permissions (owner read/write only)
|
|
264
|
+
- Atomic file operations (write to temp, then rename)
|
|
265
|
+
- Path validation (no traversal)
|
|
266
|
+
|
|
267
|
+
Performance:
|
|
268
|
+
- Target: <1 second for files <10MB
|
|
269
|
+
- Uses shutil.copy2 (preserves metadata)
|
|
270
|
+
"""
|
|
271
|
+
start_time = time.time()
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
# Validate file exists
|
|
275
|
+
if not file_path.exists():
|
|
276
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
277
|
+
|
|
278
|
+
# Validate path (prevent traversal)
|
|
279
|
+
if not self._is_safe_path(file_path):
|
|
280
|
+
raise BackupFailedError(
|
|
281
|
+
file_path, "Path traversal detected or unsafe path"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Calculate checksum
|
|
285
|
+
checksum = self._calculate_checksum(file_path)
|
|
286
|
+
size_bytes = file_path.stat().st_size
|
|
287
|
+
|
|
288
|
+
# Create backup directory
|
|
289
|
+
if backup_dir is None:
|
|
290
|
+
backup_dir = Path(self.config.backup_location)
|
|
291
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
|
|
293
|
+
# Generate backup filename
|
|
294
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
295
|
+
backup_filename = f"{file_path.stem}.backup_{timestamp}{file_path.suffix}"
|
|
296
|
+
backup_path = backup_dir / backup_filename
|
|
297
|
+
|
|
298
|
+
# Copy file to backup (atomic operation)
|
|
299
|
+
temp_path = backup_path.with_suffix(".tmp")
|
|
300
|
+
try:
|
|
301
|
+
shutil.copy2(file_path, temp_path)
|
|
302
|
+
# Set restrictive permissions (0o600 = owner read/write only)
|
|
303
|
+
os.chmod(temp_path, 0o600)
|
|
304
|
+
# Atomic rename
|
|
305
|
+
os.replace(temp_path, backup_path)
|
|
306
|
+
finally:
|
|
307
|
+
# Clean up temp file if it exists
|
|
308
|
+
if temp_path.exists():
|
|
309
|
+
temp_path.unlink()
|
|
310
|
+
|
|
311
|
+
metadata = BackupMetadata(
|
|
312
|
+
original_path=file_path,
|
|
313
|
+
backup_path=backup_path,
|
|
314
|
+
timestamp=datetime.now(),
|
|
315
|
+
checksum=checksum,
|
|
316
|
+
size_bytes=size_bytes,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
duration = time.time() - start_time
|
|
320
|
+
self.logger.info(
|
|
321
|
+
f"Created backup: {backup_path} (checksum: {checksum[:8]}..., "
|
|
322
|
+
f"size: {size_bytes} bytes, duration: {duration:.2f}s)"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return metadata
|
|
326
|
+
|
|
327
|
+
except FileNotFoundError:
|
|
328
|
+
raise
|
|
329
|
+
except Exception as e:
|
|
330
|
+
raise BackupFailedError(file_path, str(e)) from e
|
|
331
|
+
|
|
332
|
+
def validate_backup(self, backup: BackupMetadata) -> bool:
|
|
333
|
+
"""
|
|
334
|
+
Validate backup integrity using checksum.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
backup: Backup metadata to validate
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
True if backup is valid, False otherwise
|
|
341
|
+
"""
|
|
342
|
+
try:
|
|
343
|
+
if not backup.backup_path.exists():
|
|
344
|
+
self.logger.warning(f"Backup file not found: {backup.backup_path}")
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
current_checksum = self._calculate_checksum(backup.backup_path)
|
|
348
|
+
is_valid = current_checksum == backup.checksum
|
|
349
|
+
|
|
350
|
+
if not is_valid:
|
|
351
|
+
self.logger.warning(
|
|
352
|
+
f"Backup checksum mismatch: expected {backup.checksum[:8]}..., "
|
|
353
|
+
f"got {current_checksum[:8]}..."
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return is_valid
|
|
357
|
+
|
|
358
|
+
except Exception as e:
|
|
359
|
+
self.logger.error(f"Error validating backup: {e}")
|
|
360
|
+
return False
|
|
361
|
+
|
|
362
|
+
def cleanup_old_backups(
|
|
363
|
+
self, file_path: Path, keep_count: int | None = None
|
|
364
|
+
) -> int:
|
|
365
|
+
"""
|
|
366
|
+
Clean up old backups, keeping only recent N.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
file_path: Original file path
|
|
370
|
+
keep_count: Number of backups to keep (default: from config)
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Number of backups deleted
|
|
374
|
+
"""
|
|
375
|
+
if keep_count is None:
|
|
376
|
+
keep_count = self.config.max_backup_age_days # Reuse config value
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
backups = self.list_backups(file_path)
|
|
380
|
+
|
|
381
|
+
# Keep only N most recent backups
|
|
382
|
+
if len(backups) <= keep_count:
|
|
383
|
+
return 0
|
|
384
|
+
|
|
385
|
+
backups_to_delete = backups[keep_count:]
|
|
386
|
+
deleted_count = 0
|
|
387
|
+
|
|
388
|
+
for backup in backups_to_delete:
|
|
389
|
+
try:
|
|
390
|
+
backup.backup_path.unlink()
|
|
391
|
+
deleted_count += 1
|
|
392
|
+
self.logger.debug(f"Deleted old backup: {backup.backup_path}")
|
|
393
|
+
except Exception as e:
|
|
394
|
+
self.logger.warning(f"Failed to delete backup {backup.backup_path}: {e}")
|
|
395
|
+
|
|
396
|
+
return deleted_count
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
self.logger.error(f"Error cleaning up backups: {e}")
|
|
400
|
+
return 0
|
|
401
|
+
|
|
402
|
+
def list_backups(self, file_path: Path) -> list[BackupMetadata]:
|
|
403
|
+
"""
|
|
404
|
+
List all backups for a file, sorted by timestamp (newest first).
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
file_path: Original file path
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
List of backup metadata
|
|
411
|
+
"""
|
|
412
|
+
backup_dir = Path(self.config.backup_location)
|
|
413
|
+
if not backup_dir.exists():
|
|
414
|
+
return []
|
|
415
|
+
|
|
416
|
+
# Find backup files matching pattern
|
|
417
|
+
pattern = f"{file_path.stem}.backup_*{file_path.suffix}"
|
|
418
|
+
backup_files = list(backup_dir.glob(pattern))
|
|
419
|
+
|
|
420
|
+
backups = []
|
|
421
|
+
for backup_file in backup_files:
|
|
422
|
+
try:
|
|
423
|
+
# Extract timestamp from filename
|
|
424
|
+
timestamp_str = backup_file.stem.split(".backup_")[1]
|
|
425
|
+
timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
|
426
|
+
|
|
427
|
+
checksum = self._calculate_checksum(backup_file)
|
|
428
|
+
size_bytes = backup_file.stat().st_size
|
|
429
|
+
|
|
430
|
+
metadata = BackupMetadata(
|
|
431
|
+
original_path=file_path,
|
|
432
|
+
backup_path=backup_file,
|
|
433
|
+
timestamp=timestamp,
|
|
434
|
+
checksum=checksum,
|
|
435
|
+
size_bytes=size_bytes,
|
|
436
|
+
)
|
|
437
|
+
backups.append(metadata)
|
|
438
|
+
|
|
439
|
+
except Exception as e:
|
|
440
|
+
self.logger.warning(f"Failed to parse backup {backup_file}: {e}")
|
|
441
|
+
|
|
442
|
+
# Sort by timestamp (newest first)
|
|
443
|
+
backups.sort(key=lambda b: b.timestamp, reverse=True)
|
|
444
|
+
return backups
|
|
445
|
+
|
|
446
|
+
def _calculate_checksum(self, file_path: Path) -> str:
|
|
447
|
+
"""Calculate SHA-256 checksum of file."""
|
|
448
|
+
sha256 = hashlib.sha256()
|
|
449
|
+
with open(file_path, "rb") as f:
|
|
450
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
451
|
+
sha256.update(chunk)
|
|
452
|
+
return sha256.hexdigest()
|
|
453
|
+
|
|
454
|
+
def _is_safe_path(self, file_path: Path) -> bool:
|
|
455
|
+
"""
|
|
456
|
+
Validate path is safe (no traversal, within project).
|
|
457
|
+
|
|
458
|
+
Security: Prevent path traversal attacks.
|
|
459
|
+
"""
|
|
460
|
+
try:
|
|
461
|
+
# Resolve to absolute path
|
|
462
|
+
abs_path = file_path.resolve()
|
|
463
|
+
|
|
464
|
+
# Check for path traversal (../)
|
|
465
|
+
if ".." in str(file_path):
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
# Check file exists or parent exists (for new files)
|
|
469
|
+
if not abs_path.exists() and not abs_path.parent.exists():
|
|
470
|
+
return False
|
|
471
|
+
|
|
472
|
+
return True
|
|
473
|
+
|
|
474
|
+
except Exception:
|
|
475
|
+
return False
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
# ============================================================================
|
|
479
|
+
# ValidationManager
|
|
480
|
+
# ============================================================================
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
class ValidationManager:
|
|
484
|
+
"""
|
|
485
|
+
Validate code with multiple checks.
|
|
486
|
+
|
|
487
|
+
Validation types:
|
|
488
|
+
- Syntax validation (AST parsing)
|
|
489
|
+
- Import validation (check imports exist)
|
|
490
|
+
- Linting validation (Ruff clean run)
|
|
491
|
+
|
|
492
|
+
Thread-safe: Yes (no shared state)
|
|
493
|
+
Async: Yes (uses async subprocess)
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
def __init__(self, config: AutoFixConfig):
|
|
497
|
+
"""
|
|
498
|
+
Initialize ValidationManager.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
config: Auto-fix configuration
|
|
502
|
+
"""
|
|
503
|
+
self.config = config
|
|
504
|
+
self.logger = logging.getLogger(__name__)
|
|
505
|
+
|
|
506
|
+
async def validate_syntax(self, file_path: Path) -> ValidationResult:
|
|
507
|
+
"""
|
|
508
|
+
Validate Python syntax using AST parsing.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
file_path: Path to file to validate
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
ValidationResult with syntax validation status
|
|
515
|
+
|
|
516
|
+
Performance:
|
|
517
|
+
- Target: <100ms for files <1000 lines
|
|
518
|
+
- Uses ast.parse (no subprocess)
|
|
519
|
+
"""
|
|
520
|
+
errors = []
|
|
521
|
+
warnings = []
|
|
522
|
+
|
|
523
|
+
try:
|
|
524
|
+
with open(file_path, encoding="utf-8") as f:
|
|
525
|
+
code = f.read()
|
|
526
|
+
|
|
527
|
+
# Parse AST
|
|
528
|
+
ast.parse(code, filename=str(file_path))
|
|
529
|
+
syntax_valid = True
|
|
530
|
+
|
|
531
|
+
except SyntaxError as e:
|
|
532
|
+
syntax_valid = False
|
|
533
|
+
errors.append(f"Syntax error at line {e.lineno}: {e.msg}")
|
|
534
|
+
self.logger.warning(f"Syntax validation failed: {e}")
|
|
535
|
+
|
|
536
|
+
except Exception as e:
|
|
537
|
+
syntax_valid = False
|
|
538
|
+
errors.append(f"Unexpected error during syntax validation: {e}")
|
|
539
|
+
self.logger.error(f"Syntax validation error: {e}")
|
|
540
|
+
|
|
541
|
+
return ValidationResult(
|
|
542
|
+
passed=syntax_valid,
|
|
543
|
+
syntax_valid=syntax_valid,
|
|
544
|
+
imports_valid=True, # Not checked in this method
|
|
545
|
+
linting_valid=True, # Not checked in this method
|
|
546
|
+
errors=errors,
|
|
547
|
+
warnings=warnings,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
async def validate_imports(self, file_path: Path) -> ValidationResult:
|
|
551
|
+
"""
|
|
552
|
+
Validate imports are resolvable.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
file_path: Path to file to validate
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
ValidationResult with import validation status
|
|
559
|
+
|
|
560
|
+
Performance:
|
|
561
|
+
- Target: <200ms
|
|
562
|
+
- Parses imports from AST
|
|
563
|
+
- Checks module existence
|
|
564
|
+
"""
|
|
565
|
+
errors = []
|
|
566
|
+
warnings = []
|
|
567
|
+
imports_valid = True
|
|
568
|
+
|
|
569
|
+
try:
|
|
570
|
+
with open(file_path, encoding="utf-8") as f:
|
|
571
|
+
code = f.read()
|
|
572
|
+
|
|
573
|
+
tree = ast.parse(code, filename=str(file_path))
|
|
574
|
+
|
|
575
|
+
# Extract imports
|
|
576
|
+
for node in ast.walk(tree):
|
|
577
|
+
if isinstance(node, ast.Import):
|
|
578
|
+
for alias in node.names:
|
|
579
|
+
if not self._check_import(alias.name):
|
|
580
|
+
warnings.append(f"Cannot resolve import: {alias.name}")
|
|
581
|
+
|
|
582
|
+
elif isinstance(node, ast.ImportFrom):
|
|
583
|
+
if node.module and not self._check_import(node.module):
|
|
584
|
+
warnings.append(f"Cannot resolve import: {node.module}")
|
|
585
|
+
|
|
586
|
+
except Exception as e:
|
|
587
|
+
imports_valid = False
|
|
588
|
+
errors.append(f"Import validation error: {e}")
|
|
589
|
+
self.logger.error(f"Import validation failed: {e}")
|
|
590
|
+
|
|
591
|
+
return ValidationResult(
|
|
592
|
+
passed=imports_valid and not errors,
|
|
593
|
+
syntax_valid=True, # Not checked in this method
|
|
594
|
+
imports_valid=imports_valid,
|
|
595
|
+
linting_valid=True, # Not checked in this method
|
|
596
|
+
errors=errors,
|
|
597
|
+
warnings=warnings,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
async def validate_linting(self, file_path: Path) -> ValidationResult:
|
|
601
|
+
"""
|
|
602
|
+
Validate linting with Ruff (check for new issues).
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
file_path: Path to file to validate
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
ValidationResult with linting validation status
|
|
609
|
+
|
|
610
|
+
Performance:
|
|
611
|
+
- Target: <500ms
|
|
612
|
+
- Uses `ruff check` subprocess
|
|
613
|
+
"""
|
|
614
|
+
errors = []
|
|
615
|
+
warnings = []
|
|
616
|
+
linting_valid = True
|
|
617
|
+
|
|
618
|
+
try:
|
|
619
|
+
# Run Ruff check (no --fix)
|
|
620
|
+
result = await asyncio.create_subprocess_exec(
|
|
621
|
+
"ruff",
|
|
622
|
+
"check",
|
|
623
|
+
"--output-format=json",
|
|
624
|
+
str(file_path),
|
|
625
|
+
stdout=asyncio.subprocess.PIPE,
|
|
626
|
+
stderr=asyncio.subprocess.PIPE,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
stdout, stderr = await asyncio.wait_for(
|
|
630
|
+
result.communicate(), timeout=self.config.timeout
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
if result.returncode != 0:
|
|
634
|
+
# Parse JSON output
|
|
635
|
+
try:
|
|
636
|
+
issues = json.loads(stdout.decode())
|
|
637
|
+
if issues:
|
|
638
|
+
linting_valid = False
|
|
639
|
+
for issue in issues:
|
|
640
|
+
errors.append(
|
|
641
|
+
f"Line {issue.get('location', {}).get('row', '?')}: "
|
|
642
|
+
f"{issue.get('message', 'Unknown error')} "
|
|
643
|
+
f"[{issue.get('code', 'unknown')}]"
|
|
644
|
+
)
|
|
645
|
+
except json.JSONDecodeError:
|
|
646
|
+
# Ruff output is not JSON, treat as warning
|
|
647
|
+
warnings.append("Could not parse Ruff output")
|
|
648
|
+
|
|
649
|
+
except TimeoutError:
|
|
650
|
+
warnings.append(f"Ruff validation timed out after {self.config.timeout}s")
|
|
651
|
+
except FileNotFoundError:
|
|
652
|
+
warnings.append("Ruff not found (skipping linting validation)")
|
|
653
|
+
except Exception as e:
|
|
654
|
+
warnings.append(f"Linting validation error: {e}")
|
|
655
|
+
self.logger.error(f"Linting validation failed: {e}")
|
|
656
|
+
|
|
657
|
+
return ValidationResult(
|
|
658
|
+
passed=linting_valid and not errors,
|
|
659
|
+
syntax_valid=True, # Not checked in this method
|
|
660
|
+
imports_valid=True, # Not checked in this method
|
|
661
|
+
linting_valid=linting_valid,
|
|
662
|
+
errors=errors,
|
|
663
|
+
warnings=warnings,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
async def validate_all(self, file_path: Path) -> ValidationResult:
|
|
667
|
+
"""
|
|
668
|
+
Run all validation checks.
|
|
669
|
+
|
|
670
|
+
Args:
|
|
671
|
+
file_path: Path to file to validate
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
ValidationResult with all validation statuses
|
|
675
|
+
|
|
676
|
+
Performance:
|
|
677
|
+
- Target: <1 second
|
|
678
|
+
- Fails fast on first error
|
|
679
|
+
"""
|
|
680
|
+
all_errors = []
|
|
681
|
+
all_warnings = []
|
|
682
|
+
|
|
683
|
+
# Syntax validation (fast, fail fast)
|
|
684
|
+
syntax_result = await self.validate_syntax(file_path)
|
|
685
|
+
all_errors.extend(syntax_result.errors)
|
|
686
|
+
all_warnings.extend(syntax_result.warnings)
|
|
687
|
+
|
|
688
|
+
if not syntax_result.syntax_valid:
|
|
689
|
+
# Fail fast - syntax errors prevent other checks
|
|
690
|
+
return ValidationResult(
|
|
691
|
+
passed=False,
|
|
692
|
+
syntax_valid=False,
|
|
693
|
+
imports_valid=False,
|
|
694
|
+
linting_valid=False,
|
|
695
|
+
errors=all_errors,
|
|
696
|
+
warnings=all_warnings,
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
# Import validation
|
|
700
|
+
import_result = await self.validate_imports(file_path)
|
|
701
|
+
all_errors.extend(import_result.errors)
|
|
702
|
+
all_warnings.extend(import_result.warnings)
|
|
703
|
+
|
|
704
|
+
# Linting validation
|
|
705
|
+
linting_result = await self.validate_linting(file_path)
|
|
706
|
+
all_errors.extend(linting_result.errors)
|
|
707
|
+
all_warnings.extend(linting_result.warnings)
|
|
708
|
+
|
|
709
|
+
passed = (
|
|
710
|
+
syntax_result.syntax_valid
|
|
711
|
+
and import_result.imports_valid
|
|
712
|
+
and linting_result.linting_valid
|
|
713
|
+
and not all_errors
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
return ValidationResult(
|
|
717
|
+
passed=passed,
|
|
718
|
+
syntax_valid=syntax_result.syntax_valid,
|
|
719
|
+
imports_valid=import_result.imports_valid,
|
|
720
|
+
linting_valid=linting_result.linting_valid,
|
|
721
|
+
errors=all_errors,
|
|
722
|
+
warnings=all_warnings,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
def _check_import(self, module_name: str) -> bool:
|
|
726
|
+
"""
|
|
727
|
+
Check if module can be imported.
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
True if module exists, False otherwise
|
|
731
|
+
"""
|
|
732
|
+
try:
|
|
733
|
+
__import__(module_name.split(".")[0])
|
|
734
|
+
return True
|
|
735
|
+
except ImportError:
|
|
736
|
+
return False
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
# ============================================================================
|
|
740
|
+
# RestoreManager
|
|
741
|
+
# ============================================================================
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
class RestoreManager:
|
|
745
|
+
"""
|
|
746
|
+
Restore files from backups with atomic operations.
|
|
747
|
+
|
|
748
|
+
Features:
|
|
749
|
+
- Atomic restore (write to temp, then rename)
|
|
750
|
+
- Checksum verification
|
|
751
|
+
- Metadata preservation
|
|
752
|
+
|
|
753
|
+
Thread-safe: Yes (atomic operations)
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
def __init__(self, config: AutoFixConfig):
|
|
757
|
+
"""
|
|
758
|
+
Initialize RestoreManager.
|
|
759
|
+
|
|
760
|
+
Args:
|
|
761
|
+
config: Auto-fix configuration
|
|
762
|
+
"""
|
|
763
|
+
self.config = config
|
|
764
|
+
self.logger = logging.getLogger(__name__)
|
|
765
|
+
|
|
766
|
+
async def restore(self, backup: BackupMetadata) -> None:
|
|
767
|
+
"""
|
|
768
|
+
Restore file from backup with verification.
|
|
769
|
+
|
|
770
|
+
Args:
|
|
771
|
+
backup: Backup metadata
|
|
772
|
+
|
|
773
|
+
Raises:
|
|
774
|
+
RestoreFailedError: If restore fails
|
|
775
|
+
FileNotFoundError: If backup doesn't exist
|
|
776
|
+
|
|
777
|
+
Performance:
|
|
778
|
+
- Target: <1 second
|
|
779
|
+
- Uses atomic rename
|
|
780
|
+
|
|
781
|
+
Security:
|
|
782
|
+
- Validates checksum after restore
|
|
783
|
+
- Uses atomic operations (no partial states)
|
|
784
|
+
"""
|
|
785
|
+
try:
|
|
786
|
+
if not backup.backup_path.exists():
|
|
787
|
+
raise FileNotFoundError(f"Backup not found: {backup.backup_path}")
|
|
788
|
+
|
|
789
|
+
# Verify backup integrity
|
|
790
|
+
current_checksum = self._calculate_checksum(backup.backup_path)
|
|
791
|
+
if current_checksum != backup.checksum:
|
|
792
|
+
raise RestoreFailedError(
|
|
793
|
+
backup, f"Backup checksum mismatch: {current_checksum[:8]}..."
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Copy backup to temp location
|
|
797
|
+
temp_path = backup.original_path.with_suffix(".restore_tmp")
|
|
798
|
+
try:
|
|
799
|
+
shutil.copy2(backup.backup_path, temp_path)
|
|
800
|
+
# Atomic rename
|
|
801
|
+
os.replace(temp_path, backup.original_path)
|
|
802
|
+
|
|
803
|
+
self.logger.info(f"Restored file from backup: {backup.backup_path}")
|
|
804
|
+
|
|
805
|
+
finally:
|
|
806
|
+
# Clean up temp file if it exists
|
|
807
|
+
if temp_path.exists():
|
|
808
|
+
temp_path.unlink()
|
|
809
|
+
|
|
810
|
+
# Verify restore
|
|
811
|
+
if not self.verify_restore(backup.original_path, backup):
|
|
812
|
+
raise RestoreFailedError(
|
|
813
|
+
backup, "Restore verification failed (checksum mismatch)"
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
except (FileNotFoundError, RestoreFailedError):
|
|
817
|
+
raise
|
|
818
|
+
except Exception as e:
|
|
819
|
+
raise RestoreFailedError(backup, str(e)) from e
|
|
820
|
+
|
|
821
|
+
def verify_restore(self, file_path: Path, backup: BackupMetadata) -> bool:
|
|
822
|
+
"""
|
|
823
|
+
Verify restored file matches backup checksum.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
file_path: Restored file path
|
|
827
|
+
backup: Original backup metadata
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
True if checksums match, False otherwise
|
|
831
|
+
"""
|
|
832
|
+
try:
|
|
833
|
+
current_checksum = self._calculate_checksum(file_path)
|
|
834
|
+
return current_checksum == backup.checksum
|
|
835
|
+
except Exception as e:
|
|
836
|
+
self.logger.error(f"Restore verification error: {e}")
|
|
837
|
+
return False
|
|
838
|
+
|
|
839
|
+
def _calculate_checksum(self, file_path: Path) -> str:
|
|
840
|
+
"""Calculate SHA-256 checksum of file."""
|
|
841
|
+
sha256 = hashlib.sha256()
|
|
842
|
+
with open(file_path, "rb") as f:
|
|
843
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
844
|
+
sha256.update(chunk)
|
|
845
|
+
return sha256.hexdigest()
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
# ============================================================================
|
|
849
|
+
# AutoFixModule (Main Orchestrator)
|
|
850
|
+
# ============================================================================
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
class AutoFixModule:
|
|
854
|
+
"""
|
|
855
|
+
Auto-fix module for ImplementerAgent.
|
|
856
|
+
|
|
857
|
+
Orchestrates backup → fix → validate → restore workflow.
|
|
858
|
+
Integrates with Ruff for auto-fixes.
|
|
859
|
+
|
|
860
|
+
Thread-safe: Yes (immutable results, no shared state)
|
|
861
|
+
Async: Yes (uses async subprocess)
|
|
862
|
+
"""
|
|
863
|
+
|
|
864
|
+
def __init__(
|
|
865
|
+
self,
|
|
866
|
+
config: AutoFixConfig,
|
|
867
|
+
backup_manager: BackupManager | None = None,
|
|
868
|
+
validation_manager: ValidationManager | None = None,
|
|
869
|
+
restore_manager: RestoreManager | None = None,
|
|
870
|
+
):
|
|
871
|
+
"""
|
|
872
|
+
Initialize AutoFixModule.
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
config: Auto-fix configuration
|
|
876
|
+
backup_manager: Optional custom backup manager
|
|
877
|
+
validation_manager: Optional custom validation manager
|
|
878
|
+
restore_manager: Optional custom restore manager
|
|
879
|
+
|
|
880
|
+
Raises:
|
|
881
|
+
ValueError: If config is invalid
|
|
882
|
+
"""
|
|
883
|
+
self.config = config
|
|
884
|
+
self.backup_manager = backup_manager or BackupManager(config)
|
|
885
|
+
self.validation_manager = validation_manager or ValidationManager(config)
|
|
886
|
+
self.restore_manager = restore_manager or RestoreManager(config)
|
|
887
|
+
self.logger = logging.getLogger(__name__)
|
|
888
|
+
|
|
889
|
+
async def auto_fix(
|
|
890
|
+
self,
|
|
891
|
+
file_path: Path,
|
|
892
|
+
*,
|
|
893
|
+
create_backup: bool = True,
|
|
894
|
+
timeout: int | None = None,
|
|
895
|
+
) -> AutoFixResult:
|
|
896
|
+
"""
|
|
897
|
+
Apply auto-fixes to file with validation and rollback.
|
|
898
|
+
|
|
899
|
+
Workflow:
|
|
900
|
+
1. Validate file path (security check)
|
|
901
|
+
2. Create backup (if enabled)
|
|
902
|
+
3. Run `ruff check --fix`
|
|
903
|
+
4. Validate fixes (syntax, imports, linting)
|
|
904
|
+
5. Rollback if validation fails
|
|
905
|
+
6. Return result
|
|
906
|
+
|
|
907
|
+
Args:
|
|
908
|
+
file_path: Path to file to auto-fix
|
|
909
|
+
create_backup: Whether to create backup before fixing (default: True)
|
|
910
|
+
timeout: Optional timeout in seconds (default: from config)
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
AutoFixResult with success status and metadata
|
|
914
|
+
|
|
915
|
+
Raises:
|
|
916
|
+
Never raises - returns failure result instead
|
|
917
|
+
|
|
918
|
+
Performance:
|
|
919
|
+
- Target: <5 seconds for files <1000 lines
|
|
920
|
+
- Backup: <1 second
|
|
921
|
+
- Fix: <3 seconds
|
|
922
|
+
- Validation: <1 second
|
|
923
|
+
|
|
924
|
+
Security:
|
|
925
|
+
- Validates file path is within project
|
|
926
|
+
- Uses subprocess with list args (no shell injection)
|
|
927
|
+
- Creates backup with restrictive permissions (0o600)
|
|
928
|
+
"""
|
|
929
|
+
start_time = time.time()
|
|
930
|
+
errors = []
|
|
931
|
+
warnings = []
|
|
932
|
+
backup_metadata = None
|
|
933
|
+
fixes_applied = 0
|
|
934
|
+
|
|
935
|
+
try:
|
|
936
|
+
# Validate file path
|
|
937
|
+
if not file_path.exists():
|
|
938
|
+
errors.append(f"File not found: {file_path}")
|
|
939
|
+
return self._create_error_result(errors, warnings, start_time)
|
|
940
|
+
|
|
941
|
+
if not self.backup_manager._is_safe_path(file_path):
|
|
942
|
+
errors.append("Unsafe file path (path traversal detected)")
|
|
943
|
+
return self._create_error_result(errors, warnings, start_time)
|
|
944
|
+
|
|
945
|
+
# Step 1: Create backup
|
|
946
|
+
if create_backup and self.config.create_backup:
|
|
947
|
+
try:
|
|
948
|
+
backup_metadata = self.backup_manager.create_backup(file_path)
|
|
949
|
+
self.logger.info(f"Backup created: {backup_metadata.backup_path}")
|
|
950
|
+
except BackupFailedError as e:
|
|
951
|
+
errors.append(f"Backup failed: {e.reason}")
|
|
952
|
+
return self._create_error_result(
|
|
953
|
+
errors, warnings, start_time, backup_metadata=None
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
# Step 2: Run ruff check --fix
|
|
957
|
+
try:
|
|
958
|
+
fix_timeout = timeout or self.config.timeout
|
|
959
|
+
result = await asyncio.create_subprocess_exec(
|
|
960
|
+
"ruff",
|
|
961
|
+
"check",
|
|
962
|
+
"--fix",
|
|
963
|
+
"--output-format=json",
|
|
964
|
+
str(file_path),
|
|
965
|
+
stdout=asyncio.subprocess.PIPE,
|
|
966
|
+
stderr=asyncio.subprocess.PIPE,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
stdout, stderr = await asyncio.wait_for(
|
|
970
|
+
result.communicate(), timeout=fix_timeout
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
# Count fixes applied from JSON output
|
|
974
|
+
try:
|
|
975
|
+
output = json.loads(stdout.decode())
|
|
976
|
+
fixes_applied = len(
|
|
977
|
+
[issue for issue in output if issue.get("fixed", False)]
|
|
978
|
+
)
|
|
979
|
+
except json.JSONDecodeError:
|
|
980
|
+
# Cannot parse output, assume some fixes applied if returncode == 0
|
|
981
|
+
fixes_applied = 1 if result.returncode == 0 else 0
|
|
982
|
+
|
|
983
|
+
self.logger.info(f"Applied {fixes_applied} auto-fixes")
|
|
984
|
+
|
|
985
|
+
except TimeoutError:
|
|
986
|
+
errors.append(f"Ruff timed out after {fix_timeout}s")
|
|
987
|
+
await self._rollback_if_needed(backup_metadata, errors)
|
|
988
|
+
return self._create_error_result(
|
|
989
|
+
errors, warnings, start_time, backup_metadata
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
except FileNotFoundError:
|
|
993
|
+
warnings.append("Ruff not found (skipping auto-fix)")
|
|
994
|
+
return self._create_error_result(
|
|
995
|
+
errors, warnings, start_time, backup_metadata
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
except Exception as e:
|
|
999
|
+
errors.append(f"Ruff execution failed: {e}")
|
|
1000
|
+
await self._rollback_if_needed(backup_metadata, errors)
|
|
1001
|
+
return self._create_error_result(
|
|
1002
|
+
errors, warnings, start_time, backup_metadata
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
# Step 3: Validate fixes
|
|
1006
|
+
if self.config.validation_required:
|
|
1007
|
+
validation_result = await self.validation_manager.validate_all(file_path)
|
|
1008
|
+
|
|
1009
|
+
if not validation_result.passed:
|
|
1010
|
+
errors.extend(validation_result.errors)
|
|
1011
|
+
warnings.extend(validation_result.warnings)
|
|
1012
|
+
|
|
1013
|
+
# Rollback on validation failure
|
|
1014
|
+
await self._rollback_if_needed(backup_metadata, errors)
|
|
1015
|
+
|
|
1016
|
+
return AutoFixResult(
|
|
1017
|
+
success=False,
|
|
1018
|
+
fixes_applied=fixes_applied,
|
|
1019
|
+
validation_passed=False,
|
|
1020
|
+
backup_created=backup_metadata is not None,
|
|
1021
|
+
backup_metadata=backup_metadata,
|
|
1022
|
+
errors=errors,
|
|
1023
|
+
warnings=warnings,
|
|
1024
|
+
duration_seconds=time.time() - start_time,
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
warnings.extend(validation_result.warnings)
|
|
1028
|
+
|
|
1029
|
+
# Success
|
|
1030
|
+
duration = time.time() - start_time
|
|
1031
|
+
self.logger.info(
|
|
1032
|
+
f"Auto-fix completed successfully: {fixes_applied} fixes in {duration:.2f}s"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
return AutoFixResult(
|
|
1036
|
+
success=True,
|
|
1037
|
+
fixes_applied=fixes_applied,
|
|
1038
|
+
validation_passed=True,
|
|
1039
|
+
backup_created=backup_metadata is not None,
|
|
1040
|
+
backup_metadata=backup_metadata,
|
|
1041
|
+
errors=[],
|
|
1042
|
+
warnings=warnings,
|
|
1043
|
+
duration_seconds=duration,
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
except Exception as e:
|
|
1047
|
+
# Unexpected error - never raise, return error result
|
|
1048
|
+
errors.append(f"Unexpected error: {e}")
|
|
1049
|
+
self.logger.exception(f"Auto-fix failed with unexpected error: {e}")
|
|
1050
|
+
await self._rollback_if_needed(backup_metadata, errors)
|
|
1051
|
+
return self._create_error_result(errors, warnings, start_time, backup_metadata)
|
|
1052
|
+
|
|
1053
|
+
async def validate_fixes(self, file_path: Path) -> ValidationResult:
|
|
1054
|
+
"""
|
|
1055
|
+
Validate fixes without applying them (dry-run check).
|
|
1056
|
+
|
|
1057
|
+
Args:
|
|
1058
|
+
file_path: Path to file to validate
|
|
1059
|
+
|
|
1060
|
+
Returns:
|
|
1061
|
+
ValidationResult with validation status
|
|
1062
|
+
|
|
1063
|
+
Raises:
|
|
1064
|
+
FileNotFoundError: If file doesn't exist
|
|
1065
|
+
"""
|
|
1066
|
+
if not file_path.exists():
|
|
1067
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
1068
|
+
|
|
1069
|
+
return await self.validation_manager.validate_all(file_path)
|
|
1070
|
+
|
|
1071
|
+
async def rollback(self, backup_metadata: BackupMetadata) -> None:
|
|
1072
|
+
"""
|
|
1073
|
+
Manually rollback to backup.
|
|
1074
|
+
|
|
1075
|
+
Args:
|
|
1076
|
+
backup_metadata: Backup to restore from
|
|
1077
|
+
|
|
1078
|
+
Raises:
|
|
1079
|
+
RestoreFailedError: If restore fails
|
|
1080
|
+
FileNotFoundError: If backup doesn't exist
|
|
1081
|
+
"""
|
|
1082
|
+
await self.restore_manager.restore(backup_metadata)
|
|
1083
|
+
|
|
1084
|
+
async def _rollback_if_needed(
|
|
1085
|
+
self, backup_metadata: BackupMetadata | None, errors: list[str]
|
|
1086
|
+
) -> None:
|
|
1087
|
+
"""
|
|
1088
|
+
Rollback to backup if available.
|
|
1089
|
+
|
|
1090
|
+
Args:
|
|
1091
|
+
backup_metadata: Backup to restore from (None = no rollback)
|
|
1092
|
+
errors: List to append rollback errors to
|
|
1093
|
+
"""
|
|
1094
|
+
if backup_metadata is not None:
|
|
1095
|
+
try:
|
|
1096
|
+
await self.restore_manager.restore(backup_metadata)
|
|
1097
|
+
self.logger.info(f"Rolled back to backup: {backup_metadata.backup_path}")
|
|
1098
|
+
except RestoreFailedError as e:
|
|
1099
|
+
errors.append(f"Rollback failed: {e.reason}")
|
|
1100
|
+
self.logger.error(f"Rollback failed: {e}")
|
|
1101
|
+
|
|
1102
|
+
def _create_error_result(
|
|
1103
|
+
self,
|
|
1104
|
+
errors: list[str],
|
|
1105
|
+
warnings: list[str],
|
|
1106
|
+
start_time: float,
|
|
1107
|
+
backup_metadata: BackupMetadata | None = None,
|
|
1108
|
+
) -> AutoFixResult:
|
|
1109
|
+
"""Create error result with standard fields."""
|
|
1110
|
+
return AutoFixResult(
|
|
1111
|
+
success=False,
|
|
1112
|
+
fixes_applied=0,
|
|
1113
|
+
validation_passed=False,
|
|
1114
|
+
backup_created=backup_metadata is not None,
|
|
1115
|
+
backup_metadata=backup_metadata,
|
|
1116
|
+
errors=errors,
|
|
1117
|
+
warnings=warnings,
|
|
1118
|
+
duration_seconds=time.time() - start_time,
|
|
1119
|
+
)
|