tapps-agents 3.5.40__py3-none-any.whl → 3.6.0__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/scoring.py +1566 -1566
- tapps_agents/agents/reviewer/service_discovery.py +534 -534
- tapps_agents/agents/reviewer/tools/__init__.py +41 -41
- 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/health.py +665 -665
- 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 -219
- tapps_agents/cli/commands/tester.py +191 -191
- tapps_agents/cli/commands/top_level.py +3586 -3586
- 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/artifact_context_builder.py +293 -0
- 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/config.py +33 -0
- 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.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/orchestrator.py +271 -271
- 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/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/base.py +185 -185
- 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/build_orchestrator.py +2700 -2667
- 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/fix_orchestrator.py +723 -723
- 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_executor.py +2337 -2196
- 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/message_formatter.py +188 -188
- 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 -111
- 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.5.40.dist-info → tapps_agents-3.6.0.dist-info}/METADATA +672 -672
- tapps_agents-3.6.0.dist-info/RECORD +758 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/licenses/LICENSE +22 -22
- tapps_agents/health/checks/outcomes.backup_20260204_064058.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064256.py +0 -324
- tapps_agents/health/checks/outcomes.backup_20260204_064600.py +0 -324
- tapps_agents-3.5.40.dist-info/RECORD +0 -760
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/WHEEL +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/entry_points.txt +0 -0
- {tapps_agents-3.5.40.dist-info → tapps_agents-3.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1080 +1,1080 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tester Agent - Generates and runs tests
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
import re
|
|
8
|
-
import shutil
|
|
9
|
-
import subprocess # nosec B404
|
|
10
|
-
import sys
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
13
|
-
|
|
14
|
-
from ...context7.agent_integration import Context7AgentHelper, get_context7_helper
|
|
15
|
-
from ...core.agent_base import BaseAgent
|
|
16
|
-
from ...core.config import ProjectConfig, load_config
|
|
17
|
-
from ...core.test_generator import TestGenerator as CoreTestGenerator
|
|
18
|
-
from ...experts.agent_integration import ExpertSupportMixin
|
|
19
|
-
from .test_generator import TestGenerator
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class TesterAgent(BaseAgent, ExpertSupportMixin):
|
|
25
|
-
"""
|
|
26
|
-
Tester Agent - Test generation and execution.
|
|
27
|
-
|
|
28
|
-
Permissions: Read, Write, Edit, Grep, Glob, Bash
|
|
29
|
-
|
|
30
|
-
⚠️ CRITICAL ACCURACY REQUIREMENT:
|
|
31
|
-
- NEVER make up, invent, or fabricate information - Only report verified facts
|
|
32
|
-
- ALWAYS verify claims by checking actual results, not just test pass/fail
|
|
33
|
-
- Verify API calls succeed - inspect response data, status codes, error messages
|
|
34
|
-
- Distinguish between code paths executing and actual functionality working
|
|
35
|
-
- Admit uncertainty explicitly when you cannot verify
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, config: ProjectConfig | None = None):
|
|
39
|
-
super().__init__(agent_id="tester", agent_name="Tester Agent", config=config)
|
|
40
|
-
# Use config if provided, otherwise load defaults
|
|
41
|
-
if config is None:
|
|
42
|
-
config = load_config()
|
|
43
|
-
self.config = config
|
|
44
|
-
|
|
45
|
-
# Initialize test generators
|
|
46
|
-
self.test_generator = TestGenerator() # Instruction-based generator
|
|
47
|
-
self.core_test_generator = CoreTestGenerator(project_root=self.project_root if hasattr(self, 'project_root') else None) # Template-based generator
|
|
48
|
-
|
|
49
|
-
# Get tester config
|
|
50
|
-
tester_config = config.agents.tester if config and config.agents else None
|
|
51
|
-
self.test_framework = (
|
|
52
|
-
tester_config.test_framework if tester_config else "pytest"
|
|
53
|
-
)
|
|
54
|
-
self.tests_dir = (
|
|
55
|
-
Path(tester_config.tests_dir)
|
|
56
|
-
if tester_config and tester_config.tests_dir
|
|
57
|
-
else Path("tests")
|
|
58
|
-
)
|
|
59
|
-
self.coverage_threshold = (
|
|
60
|
-
tester_config.coverage_threshold if tester_config else 80.0
|
|
61
|
-
)
|
|
62
|
-
self.auto_write_tests = (
|
|
63
|
-
tester_config.auto_write_tests if tester_config else True
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# Ensure tests directory exists
|
|
67
|
-
self.tests_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
-
|
|
69
|
-
# Initialize Context7 helper
|
|
70
|
-
self.context7: Context7AgentHelper | None = None
|
|
71
|
-
if config:
|
|
72
|
-
self.context7 = get_context7_helper(self, config)
|
|
73
|
-
|
|
74
|
-
# Expert registry initialization (required due to multiple inheritance MRO issue)
|
|
75
|
-
# BaseAgent.__init__() doesn't call super().__init__(), so ExpertSupportMixin.__init__()
|
|
76
|
-
# is never called via MRO. We must manually initialize to avoid AttributeError.
|
|
77
|
-
# The registry will be properly initialized in activate() via _initialize_expert_support()
|
|
78
|
-
self.expert_registry: Any | None = None
|
|
79
|
-
|
|
80
|
-
async def activate(self, project_root: Path | None = None, offline_mode: bool = False):
|
|
81
|
-
"""Activate the tester agent with expert support."""
|
|
82
|
-
# Validate that expert_registry attribute exists (safety check)
|
|
83
|
-
if not hasattr(self, 'expert_registry'):
|
|
84
|
-
raise AttributeError(
|
|
85
|
-
f"{self.__class__.__name__}.expert_registry not initialized. "
|
|
86
|
-
"This should not happen if __init__() properly initializes the attribute."
|
|
87
|
-
)
|
|
88
|
-
await super().activate(project_root, offline_mode=offline_mode)
|
|
89
|
-
# Initialize expert support
|
|
90
|
-
await self._initialize_expert_support(project_root, offline_mode=offline_mode)
|
|
91
|
-
|
|
92
|
-
def get_commands(self) -> list[dict[str, str]]:
|
|
93
|
-
"""Return list of available commands."""
|
|
94
|
-
commands = super().get_commands()
|
|
95
|
-
commands.extend(
|
|
96
|
-
[
|
|
97
|
-
{
|
|
98
|
-
"command": "*test",
|
|
99
|
-
"description": "Generate and run tests for a file",
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"command": "*generate-tests",
|
|
103
|
-
"description": "Generate tests without running",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
"command": "*generate-e2e-tests",
|
|
107
|
-
"description": "Generate end-to-end tests (requires E2E framework)",
|
|
108
|
-
},
|
|
109
|
-
{"command": "*run-tests", "description": "Run existing tests"},
|
|
110
|
-
]
|
|
111
|
-
)
|
|
112
|
-
return commands
|
|
113
|
-
|
|
114
|
-
async def run(self, command: str, **kwargs) -> dict[str, Any]:
|
|
115
|
-
"""Execute a command."""
|
|
116
|
-
if command == "test":
|
|
117
|
-
return await self.test_command(**kwargs)
|
|
118
|
-
elif command == "generate-tests":
|
|
119
|
-
return await self.generate_tests_command(**kwargs)
|
|
120
|
-
elif command == "generate-e2e-tests":
|
|
121
|
-
return await self.generate_e2e_tests_command(**kwargs)
|
|
122
|
-
elif command == "run-tests":
|
|
123
|
-
return await self.run_tests_command(**kwargs)
|
|
124
|
-
elif command == "help":
|
|
125
|
-
return self._help()
|
|
126
|
-
else:
|
|
127
|
-
return {"error": f"Unknown command: {command}"}
|
|
128
|
-
|
|
129
|
-
async def test_command(
|
|
130
|
-
self,
|
|
131
|
-
file: str | None = None,
|
|
132
|
-
test_file: str | None = None,
|
|
133
|
-
integration: bool = False,
|
|
134
|
-
focus: str | None = None,
|
|
135
|
-
**kwargs, # Accept additional kwargs to be more flexible
|
|
136
|
-
) -> dict[str, Any]:
|
|
137
|
-
"""
|
|
138
|
-
Generate and run tests for a file.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
file: Source code file to test
|
|
142
|
-
test_file: Optional path to write test file
|
|
143
|
-
integration: If True, generate integration tests
|
|
144
|
-
focus: Comma-separated list of test aspects to focus on
|
|
145
|
-
"""
|
|
146
|
-
if not file:
|
|
147
|
-
return {"error": "File path required"}
|
|
148
|
-
|
|
149
|
-
file_path = Path(file)
|
|
150
|
-
if not file_path.exists():
|
|
151
|
-
return {"error": f"File not found: {file_path}"}
|
|
152
|
-
|
|
153
|
-
# Validate path (inherited from BaseAgent)
|
|
154
|
-
try:
|
|
155
|
-
self._validate_path(file_path, max_file_size=10 * 1024 * 1024)
|
|
156
|
-
except (FileNotFoundError, ValueError) as e:
|
|
157
|
-
return {"error": str(e)}
|
|
158
|
-
|
|
159
|
-
# Build expert query with focus areas if provided
|
|
160
|
-
focus_text = ""
|
|
161
|
-
if focus:
|
|
162
|
-
focus_areas = [area.strip() for area in focus.split(",")]
|
|
163
|
-
focus_text = f" Focus specifically on: {', '.join(focus_areas)}."
|
|
164
|
-
|
|
165
|
-
# D1: Get Context7 test framework documentation using universal auto-detection
|
|
166
|
-
# Enhancement: Use _auto_fetch_context7_docs() for automatic library detection
|
|
167
|
-
framework_docs = None
|
|
168
|
-
test_framework = "pytest" # Default
|
|
169
|
-
|
|
170
|
-
try:
|
|
171
|
-
from ...core.language_detector import LanguageDetector
|
|
172
|
-
detector = LanguageDetector()
|
|
173
|
-
language = detector.detect_language(file_path)
|
|
174
|
-
code_content = file_path.read_text(encoding="utf-8") if file_path.exists() else ""
|
|
175
|
-
test_framework = self.test_generator._detect_test_framework_for_language(
|
|
176
|
-
language=language,
|
|
177
|
-
code=code_content
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
# Use universal auto-detection hook for Context7 docs
|
|
181
|
-
# This automatically detects libraries from code and test framework name in prompt
|
|
182
|
-
context7_docs = await self._auto_fetch_context7_docs(
|
|
183
|
-
code=code_content,
|
|
184
|
-
prompt=f"Generate tests using {test_framework}",
|
|
185
|
-
language=language,
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
# Extract test framework docs if available
|
|
189
|
-
if context7_docs:
|
|
190
|
-
# First try exact match with detected framework
|
|
191
|
-
if test_framework in context7_docs and context7_docs[test_framework]:
|
|
192
|
-
framework_docs = context7_docs[test_framework]
|
|
193
|
-
else:
|
|
194
|
-
# Look for any test framework in the detected libraries
|
|
195
|
-
test_frameworks = ["pytest", "jest", "vitest", "unittest", "mocha", "jasmine"]
|
|
196
|
-
for lib_name, lib_docs in context7_docs.items():
|
|
197
|
-
if lib_name.lower() in [tf.lower() for tf in test_frameworks] and lib_docs:
|
|
198
|
-
framework_docs = lib_docs
|
|
199
|
-
test_framework = lib_name
|
|
200
|
-
break
|
|
201
|
-
|
|
202
|
-
if framework_docs:
|
|
203
|
-
logger.debug(f"D1: Auto-fetched Context7 docs for {test_framework} using universal hook")
|
|
204
|
-
except Exception as e:
|
|
205
|
-
logger.debug(f"D1: Context7 auto-detection failed: {e}, continuing without Context7 docs")
|
|
206
|
-
|
|
207
|
-
# Consult Testing expert for test generation guidance
|
|
208
|
-
expert_guidance = ""
|
|
209
|
-
expert_advice = None
|
|
210
|
-
|
|
211
|
-
# Enhance expert query with Context7 best practices if available
|
|
212
|
-
context7_guidance = ""
|
|
213
|
-
if framework_docs and framework_docs.get("content"):
|
|
214
|
-
content_preview = framework_docs.get("content", "")[:1000] # First 1000 chars
|
|
215
|
-
context7_guidance = f"\n\nContext7 Best Practices for {test_framework}:\n{content_preview}"
|
|
216
|
-
|
|
217
|
-
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
218
|
-
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
219
|
-
testing_consultation = await self.expert_registry.consult(
|
|
220
|
-
query=f"Provide best practices for generating {'integration' if integration else 'unit'} tests for: {file_path.name}. Focus on test coverage, edge cases, and maintainability.{focus_text}{context7_guidance}",
|
|
221
|
-
domain="testing-strategies",
|
|
222
|
-
agent_id=self.agent_id,
|
|
223
|
-
prioritize_builtin=True,
|
|
224
|
-
)
|
|
225
|
-
if (
|
|
226
|
-
testing_consultation.confidence
|
|
227
|
-
>= testing_consultation.confidence_threshold
|
|
228
|
-
):
|
|
229
|
-
expert_guidance = testing_consultation.weighted_answer
|
|
230
|
-
expert_advice = {
|
|
231
|
-
"confidence": testing_consultation.confidence,
|
|
232
|
-
"threshold": testing_consultation.confidence_threshold,
|
|
233
|
-
"guidance": expert_guidance,
|
|
234
|
-
}
|
|
235
|
-
else:
|
|
236
|
-
expert_advice = {
|
|
237
|
-
"confidence": testing_consultation.confidence,
|
|
238
|
-
"threshold": testing_consultation.confidence_threshold,
|
|
239
|
-
"guidance": f"Low confidence expert advice: {testing_consultation.weighted_answer}",
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
# Prepare test generation instruction
|
|
243
|
-
if integration:
|
|
244
|
-
instruction = self.test_generator.prepare_integration_tests(
|
|
245
|
-
[file_path],
|
|
246
|
-
test_path=Path(test_file) if test_file else None,
|
|
247
|
-
context=None,
|
|
248
|
-
expert_guidance=expert_guidance,
|
|
249
|
-
)
|
|
250
|
-
else:
|
|
251
|
-
instruction = self.test_generator.prepare_unit_tests(
|
|
252
|
-
file_path,
|
|
253
|
-
test_path=Path(test_file) if test_file else None,
|
|
254
|
-
context=None,
|
|
255
|
-
expert_guidance=expert_guidance,
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
# Determine test file path
|
|
259
|
-
if test_file:
|
|
260
|
-
test_path = Path(test_file)
|
|
261
|
-
else:
|
|
262
|
-
# Auto-generate test file path
|
|
263
|
-
test_path = self._get_test_file_path(file_path)
|
|
264
|
-
|
|
265
|
-
# Ensure test directory exists (fix for nested path issues)
|
|
266
|
-
try:
|
|
267
|
-
test_path.parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
-
except Exception as e:
|
|
269
|
-
logger.warning(
|
|
270
|
-
f"Failed to create test directory {test_path.parent}: {e}. "
|
|
271
|
-
"Test file creation may fail.",
|
|
272
|
-
exc_info=True
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
# D2: Prepare test quality validation info (will be used after test generation)
|
|
276
|
-
quality_validation = None
|
|
277
|
-
if self.context7 and framework_docs:
|
|
278
|
-
try:
|
|
279
|
-
# Get quality standards for the test framework
|
|
280
|
-
quality_standards = await self.context7.get_documentation(
|
|
281
|
-
library=test_framework,
|
|
282
|
-
topic="quality-standards",
|
|
283
|
-
use_fuzzy_match=True
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
if quality_standards:
|
|
287
|
-
quality_validation = {
|
|
288
|
-
"framework": test_framework,
|
|
289
|
-
"standards_available": True,
|
|
290
|
-
"source": quality_standards.get("source", "unknown"),
|
|
291
|
-
}
|
|
292
|
-
else:
|
|
293
|
-
quality_validation = {
|
|
294
|
-
"framework": test_framework,
|
|
295
|
-
"standards_available": False,
|
|
296
|
-
}
|
|
297
|
-
except Exception as e:
|
|
298
|
-
logger.debug(f"D2: Quality standards lookup failed: {e}")
|
|
299
|
-
|
|
300
|
-
# Issue 4 Fix: Actually generate and write test file if auto_write_tests is enabled
|
|
301
|
-
test_code = None
|
|
302
|
-
file_written = False
|
|
303
|
-
run_result = None
|
|
304
|
-
|
|
305
|
-
if self.auto_write_tests:
|
|
306
|
-
# Use test_file if provided, otherwise use auto-generated path
|
|
307
|
-
target_test_path = test_path
|
|
308
|
-
|
|
309
|
-
# Build test generation prompt using test_generator
|
|
310
|
-
try:
|
|
311
|
-
source_code = file_path.read_text(encoding="utf-8")
|
|
312
|
-
code_analysis = self.test_generator._analyze_code(source_code, file_path)
|
|
313
|
-
|
|
314
|
-
if integration:
|
|
315
|
-
prompt = self.test_generator._build_integration_test_prompt(
|
|
316
|
-
code=source_code,
|
|
317
|
-
test_path=target_test_path,
|
|
318
|
-
context=None,
|
|
319
|
-
expert_guidance=expert_guidance,
|
|
320
|
-
)
|
|
321
|
-
else:
|
|
322
|
-
prompt = self.test_generator._build_unit_test_prompt(
|
|
323
|
-
code=source_code,
|
|
324
|
-
analysis=code_analysis,
|
|
325
|
-
test_path=target_test_path,
|
|
326
|
-
context=None,
|
|
327
|
-
expert_guidance=expert_guidance,
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
# Generate test code template using core test generator
|
|
331
|
-
test_framework_detected = self.core_test_generator.detect_test_framework(file_path)
|
|
332
|
-
|
|
333
|
-
# Use framework-specific template generation
|
|
334
|
-
if test_framework_detected == "pytest":
|
|
335
|
-
generated_path = self.core_test_generator.generate_pytest_test(
|
|
336
|
-
source_file=file_path,
|
|
337
|
-
test_content=test_code or "", # Will be generated if empty
|
|
338
|
-
output_file=target_test_path,
|
|
339
|
-
)
|
|
340
|
-
elif test_framework_detected == "jest":
|
|
341
|
-
generated_path = self.core_test_generator.generate_jest_test(
|
|
342
|
-
source_file=file_path,
|
|
343
|
-
test_content=test_code or "",
|
|
344
|
-
output_file=target_test_path,
|
|
345
|
-
)
|
|
346
|
-
elif test_framework_detected == "unittest":
|
|
347
|
-
generated_path = self.core_test_generator.generate_unittest_test(
|
|
348
|
-
source_file=file_path,
|
|
349
|
-
test_content=test_code or "",
|
|
350
|
-
output_file=target_test_path,
|
|
351
|
-
)
|
|
352
|
-
else:
|
|
353
|
-
# Fallback to original method
|
|
354
|
-
test_code = self._generate_test_template(
|
|
355
|
-
file_path=file_path,
|
|
356
|
-
code_analysis=code_analysis,
|
|
357
|
-
test_framework=test_framework,
|
|
358
|
-
expert_guidance=expert_guidance,
|
|
359
|
-
integration=integration,
|
|
360
|
-
)
|
|
361
|
-
target_test_path.write_text(test_code, encoding="utf-8")
|
|
362
|
-
generated_path = target_test_path
|
|
363
|
-
|
|
364
|
-
file_written = True
|
|
365
|
-
logger.info(f"Test file written to: {generated_path}")
|
|
366
|
-
target_test_path = generated_path # Update path
|
|
367
|
-
|
|
368
|
-
# Run tests after generating
|
|
369
|
-
run_result = await self._run_pytest(
|
|
370
|
-
test_path=target_test_path,
|
|
371
|
-
source_paths=[str(file_path)],
|
|
372
|
-
coverage=True,
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
except Exception as e:
|
|
376
|
-
logger.warning(
|
|
377
|
-
f"Failed to auto-generate test file: {e}. "
|
|
378
|
-
"Returning instruction for manual execution.",
|
|
379
|
-
exc_info=True
|
|
380
|
-
)
|
|
381
|
-
# Fall through to return instruction
|
|
382
|
-
|
|
383
|
-
result = {
|
|
384
|
-
"type": "test",
|
|
385
|
-
"instruction": instruction.to_dict(),
|
|
386
|
-
"skill_command": instruction.to_skill_command(),
|
|
387
|
-
"test_file": str(test_path),
|
|
388
|
-
"test_directory": str(test_path.parent),
|
|
389
|
-
"source_file": str(file_path), # Traceability: source under test
|
|
390
|
-
"file_written": file_written,
|
|
391
|
-
"run_result": run_result,
|
|
392
|
-
"expert_advice": expert_advice, # Include expert recommendations
|
|
393
|
-
# D1 Enhancement: Context7 framework documentation
|
|
394
|
-
"context7_framework_docs": {
|
|
395
|
-
"framework": test_framework,
|
|
396
|
-
"docs_available": framework_docs is not None,
|
|
397
|
-
"source": framework_docs.get("source") if framework_docs else None,
|
|
398
|
-
} if framework_docs else None,
|
|
399
|
-
# D2 Enhancement: Quality validation info
|
|
400
|
-
"quality_validation": quality_validation,
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if test_code and file_written:
|
|
404
|
-
result["test_code_preview"] = test_code[:500] # Preview of generated code
|
|
405
|
-
|
|
406
|
-
return result
|
|
407
|
-
|
|
408
|
-
async def generate_tests_command(
|
|
409
|
-
self,
|
|
410
|
-
file: str | None = None,
|
|
411
|
-
test_file: str | None = None,
|
|
412
|
-
integration: bool = False,
|
|
413
|
-
) -> dict[str, Any]:
|
|
414
|
-
"""
|
|
415
|
-
Generate tests without running them.
|
|
416
|
-
|
|
417
|
-
Args:
|
|
418
|
-
file: Source code file to test
|
|
419
|
-
test_file: Optional path to write test file
|
|
420
|
-
integration: If True, generate integration tests
|
|
421
|
-
"""
|
|
422
|
-
if not file:
|
|
423
|
-
return {"error": "File path required"}
|
|
424
|
-
|
|
425
|
-
file_path = Path(file)
|
|
426
|
-
if not file_path.exists():
|
|
427
|
-
return {"error": f"File not found: {file_path}"}
|
|
428
|
-
|
|
429
|
-
# Validate path (inherited from BaseAgent)
|
|
430
|
-
try:
|
|
431
|
-
self._validate_path(file_path, max_file_size=10 * 1024 * 1024)
|
|
432
|
-
except (FileNotFoundError, ValueError) as e:
|
|
433
|
-
return {"error": str(e)}
|
|
434
|
-
|
|
435
|
-
# D1: Get Context7 test framework documentation (same as test_command)
|
|
436
|
-
framework_docs = None
|
|
437
|
-
test_framework = "pytest" # Default
|
|
438
|
-
try:
|
|
439
|
-
from ...core.language_detector import LanguageDetector
|
|
440
|
-
detector = LanguageDetector()
|
|
441
|
-
language = detector.detect_language(file_path)
|
|
442
|
-
test_framework = self.test_generator._detect_test_framework_for_language(
|
|
443
|
-
language=language,
|
|
444
|
-
code=file_path.read_text(encoding="utf-8") if file_path.exists() else ""
|
|
445
|
-
)
|
|
446
|
-
except Exception as e:
|
|
447
|
-
logger.debug(f"Failed to detect test framework: {e}, using default pytest")
|
|
448
|
-
|
|
449
|
-
if self.context7:
|
|
450
|
-
try:
|
|
451
|
-
framework_docs = await self.context7.get_documentation(
|
|
452
|
-
library=test_framework,
|
|
453
|
-
topic="testing",
|
|
454
|
-
use_fuzzy_match=True
|
|
455
|
-
)
|
|
456
|
-
if framework_docs:
|
|
457
|
-
logger.debug(f"D1: Fetched Context7 docs for {test_framework}")
|
|
458
|
-
except Exception as e:
|
|
459
|
-
logger.debug(f"D1: Context7 framework docs lookup failed: {e}")
|
|
460
|
-
|
|
461
|
-
# Consult Testing expert for test generation guidance
|
|
462
|
-
expert_guidance = ""
|
|
463
|
-
context7_guidance = ""
|
|
464
|
-
if framework_docs and framework_docs.get("content"):
|
|
465
|
-
content_preview = framework_docs.get("content", "")[:1000]
|
|
466
|
-
context7_guidance = f"\n\nContext7 Best Practices for {test_framework}:\n{content_preview}"
|
|
467
|
-
|
|
468
|
-
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
469
|
-
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
470
|
-
testing_consultation = await self.expert_registry.consult(
|
|
471
|
-
query=f"Provide best practices for generating {'integration' if integration else 'unit'} tests for: {file_path.name}. Focus on test coverage, edge cases, and maintainability.{context7_guidance}",
|
|
472
|
-
domain="testing-strategies",
|
|
473
|
-
agent_id=self.agent_id,
|
|
474
|
-
prioritize_builtin=True,
|
|
475
|
-
)
|
|
476
|
-
if (
|
|
477
|
-
testing_consultation.confidence
|
|
478
|
-
>= testing_consultation.confidence_threshold
|
|
479
|
-
):
|
|
480
|
-
expert_guidance = testing_consultation.weighted_answer
|
|
481
|
-
|
|
482
|
-
# Prepare test generation instruction
|
|
483
|
-
if integration:
|
|
484
|
-
instruction = self.test_generator.prepare_integration_tests(
|
|
485
|
-
[file_path],
|
|
486
|
-
test_path=Path(test_file) if test_file else None,
|
|
487
|
-
context=None,
|
|
488
|
-
expert_guidance=expert_guidance,
|
|
489
|
-
)
|
|
490
|
-
else:
|
|
491
|
-
instruction = self.test_generator.prepare_unit_tests(
|
|
492
|
-
file_path,
|
|
493
|
-
test_path=Path(test_file) if test_file else None,
|
|
494
|
-
context=None,
|
|
495
|
-
expert_guidance=expert_guidance,
|
|
496
|
-
)
|
|
497
|
-
|
|
498
|
-
# Determine test file path
|
|
499
|
-
if test_file:
|
|
500
|
-
test_path = Path(test_file)
|
|
501
|
-
else:
|
|
502
|
-
test_path = self._get_test_file_path(file_path)
|
|
503
|
-
|
|
504
|
-
# Ensure test directory exists
|
|
505
|
-
try:
|
|
506
|
-
test_path.parent.mkdir(parents=True, exist_ok=True)
|
|
507
|
-
except Exception as e:
|
|
508
|
-
logger.warning(
|
|
509
|
-
f"Failed to create test directory {test_path.parent}: {e}. "
|
|
510
|
-
"Test file creation may fail.",
|
|
511
|
-
exc_info=True
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
# D2: Quality validation info
|
|
515
|
-
quality_validation = None
|
|
516
|
-
if self.context7 and framework_docs:
|
|
517
|
-
try:
|
|
518
|
-
quality_standards = await self.context7.get_documentation(
|
|
519
|
-
library=test_framework,
|
|
520
|
-
topic="quality-standards",
|
|
521
|
-
use_fuzzy_match=True
|
|
522
|
-
)
|
|
523
|
-
if quality_standards:
|
|
524
|
-
quality_validation = {
|
|
525
|
-
"framework": test_framework,
|
|
526
|
-
"standards_available": True,
|
|
527
|
-
"source": quality_standards.get("source", "unknown"),
|
|
528
|
-
}
|
|
529
|
-
except Exception as e:
|
|
530
|
-
logger.debug(f"D2: Quality standards lookup failed: {e}")
|
|
531
|
-
|
|
532
|
-
# Issue 4 Fix: Actually generate and write test file if auto_write_tests is enabled
|
|
533
|
-
test_code = None
|
|
534
|
-
file_written = False
|
|
535
|
-
if self.auto_write_tests:
|
|
536
|
-
# Build test generation prompt using test_generator
|
|
537
|
-
try:
|
|
538
|
-
source_code = file_path.read_text(encoding="utf-8")
|
|
539
|
-
code_analysis = self.test_generator._analyze_code(source_code, file_path)
|
|
540
|
-
|
|
541
|
-
if integration:
|
|
542
|
-
prompt = self.test_generator._build_integration_test_prompt(
|
|
543
|
-
code=source_code,
|
|
544
|
-
test_path=test_path,
|
|
545
|
-
context=None,
|
|
546
|
-
expert_guidance=expert_guidance,
|
|
547
|
-
)
|
|
548
|
-
else:
|
|
549
|
-
prompt = self.test_generator._build_unit_test_prompt(
|
|
550
|
-
code=source_code,
|
|
551
|
-
analysis=code_analysis,
|
|
552
|
-
test_path=test_path,
|
|
553
|
-
context=None,
|
|
554
|
-
expert_guidance=expert_guidance,
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
# Use implementer agent to generate test code
|
|
558
|
-
from ...agents.implementer.agent import ImplementerAgent
|
|
559
|
-
implementer = ImplementerAgent(config=self.config)
|
|
560
|
-
await implementer.activate(offline_mode=False)
|
|
561
|
-
|
|
562
|
-
# Generate test code using implementer's generate_code
|
|
563
|
-
generate_result = await implementer.generate_code(
|
|
564
|
-
specification=prompt,
|
|
565
|
-
file_path=str(test_path),
|
|
566
|
-
context=source_code,
|
|
567
|
-
language="python",
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
# Extract generated code from result
|
|
571
|
-
# Note: implementer.generate_code returns instruction, but we can use the prompt
|
|
572
|
-
# to generate code via Cursor AI if available, or create a template
|
|
573
|
-
# For now, create a basic test file template based on the analysis
|
|
574
|
-
test_code = self._generate_test_template(
|
|
575
|
-
file_path=file_path,
|
|
576
|
-
code_analysis=code_analysis,
|
|
577
|
-
test_framework=test_framework,
|
|
578
|
-
expert_guidance=expert_guidance,
|
|
579
|
-
integration=integration,
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
# Write test file
|
|
583
|
-
test_path.write_text(test_code, encoding="utf-8")
|
|
584
|
-
file_written = True
|
|
585
|
-
logger.info(f"Test file written to: {test_path}")
|
|
586
|
-
|
|
587
|
-
await implementer.close()
|
|
588
|
-
except Exception as e:
|
|
589
|
-
logger.warning(
|
|
590
|
-
f"Failed to auto-generate test file: {e}. "
|
|
591
|
-
"Returning instruction for manual execution.",
|
|
592
|
-
exc_info=True
|
|
593
|
-
)
|
|
594
|
-
# Fall through to return instruction
|
|
595
|
-
|
|
596
|
-
result = {
|
|
597
|
-
"type": "test_generation",
|
|
598
|
-
"instruction": instruction.to_dict(),
|
|
599
|
-
"skill_command": instruction.to_skill_command(),
|
|
600
|
-
"test_file": str(test_path),
|
|
601
|
-
"source_file": str(file_path), # Traceability: source under test
|
|
602
|
-
"file_written": file_written,
|
|
603
|
-
# D1 Enhancement
|
|
604
|
-
"context7_framework_docs": {
|
|
605
|
-
"framework": test_framework,
|
|
606
|
-
"docs_available": framework_docs is not None,
|
|
607
|
-
"source": framework_docs.get("source") if framework_docs else None,
|
|
608
|
-
} if framework_docs else None,
|
|
609
|
-
# D2 Enhancement
|
|
610
|
-
"quality_validation": quality_validation,
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if test_code and file_written:
|
|
614
|
-
result["test_code_preview"] = test_code[:500] # Preview of generated code
|
|
615
|
-
|
|
616
|
-
return result
|
|
617
|
-
|
|
618
|
-
async def generate_e2e_tests_command(
|
|
619
|
-
self,
|
|
620
|
-
test_file: str | None = None,
|
|
621
|
-
project_root: str | None = None,
|
|
622
|
-
) -> dict[str, Any]:
|
|
623
|
-
"""
|
|
624
|
-
Generate end-to-end (E2E) tests for the project.
|
|
625
|
-
|
|
626
|
-
Args:
|
|
627
|
-
test_file: Optional path where test will be written
|
|
628
|
-
project_root: Optional project root directory (default: current directory)
|
|
629
|
-
|
|
630
|
-
Returns:
|
|
631
|
-
Dictionary with test generation results
|
|
632
|
-
"""
|
|
633
|
-
# Determine project root
|
|
634
|
-
if project_root:
|
|
635
|
-
proj_root = Path(project_root)
|
|
636
|
-
else:
|
|
637
|
-
proj_root = self.project_root if hasattr(self, "project_root") else Path.cwd()
|
|
638
|
-
|
|
639
|
-
if not proj_root.exists():
|
|
640
|
-
return {"error": f"Project root not found: {proj_root}"}
|
|
641
|
-
|
|
642
|
-
# Consult Testing expert for E2E test generation guidance
|
|
643
|
-
expert_guidance = ""
|
|
644
|
-
expert_advice = None
|
|
645
|
-
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
646
|
-
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
647
|
-
testing_consultation = await self.expert_registry.consult(
|
|
648
|
-
query="Provide best practices for generating end-to-end (E2E) tests. Focus on test coverage, user workflows, and maintainability.",
|
|
649
|
-
domain="testing-strategies",
|
|
650
|
-
agent_id=self.agent_id,
|
|
651
|
-
prioritize_builtin=True,
|
|
652
|
-
)
|
|
653
|
-
if (
|
|
654
|
-
testing_consultation.confidence
|
|
655
|
-
>= testing_consultation.confidence_threshold
|
|
656
|
-
):
|
|
657
|
-
expert_guidance = testing_consultation.weighted_answer
|
|
658
|
-
expert_advice = {
|
|
659
|
-
"confidence": testing_consultation.confidence,
|
|
660
|
-
"threshold": testing_consultation.confidence_threshold,
|
|
661
|
-
"guidance": expert_guidance,
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
# Prepare E2E test generation instruction
|
|
665
|
-
instruction = self.test_generator.prepare_e2e_tests(
|
|
666
|
-
project_root=proj_root,
|
|
667
|
-
test_path=Path(test_file) if test_file else None,
|
|
668
|
-
context=None,
|
|
669
|
-
expert_guidance=expert_guidance,
|
|
670
|
-
)
|
|
671
|
-
|
|
672
|
-
# Check if E2E framework was detected
|
|
673
|
-
if instruction is None:
|
|
674
|
-
return {
|
|
675
|
-
"type": "e2e_test_generation",
|
|
676
|
-
"error": "No E2E testing framework detected. Please install one of: playwright, pytest-playwright, selenium, or cypress.",
|
|
677
|
-
"instruction": None,
|
|
678
|
-
"test_file": None,
|
|
679
|
-
"framework_detected": False,
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
# Determine test file path
|
|
683
|
-
if test_file:
|
|
684
|
-
test_path = Path(test_file)
|
|
685
|
-
else:
|
|
686
|
-
# Default E2E test location
|
|
687
|
-
test_path = proj_root / "tests" / "e2e" / "test_e2e.py"
|
|
688
|
-
|
|
689
|
-
return {
|
|
690
|
-
"type": "e2e_test_generation",
|
|
691
|
-
"instruction": instruction.to_dict(),
|
|
692
|
-
"skill_command": instruction.to_skill_command(),
|
|
693
|
-
"test_file": str(test_path),
|
|
694
|
-
"framework_detected": instruction.test_framework,
|
|
695
|
-
"expert_advice": expert_advice,
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
async def run_tests_command(
|
|
699
|
-
self, test_path: str | None = None, coverage: bool = True
|
|
700
|
-
) -> dict[str, Any]:
|
|
701
|
-
"""
|
|
702
|
-
Run existing tests.
|
|
703
|
-
|
|
704
|
-
Args:
|
|
705
|
-
test_path: Path to test file or directory (default: tests/)
|
|
706
|
-
coverage: Include coverage report
|
|
707
|
-
"""
|
|
708
|
-
if test_path:
|
|
709
|
-
path = Path(test_path)
|
|
710
|
-
if not path.exists():
|
|
711
|
-
return {"error": f"Test path not found: {path}"}
|
|
712
|
-
else:
|
|
713
|
-
path = self.tests_dir
|
|
714
|
-
# Check if tests directory exists
|
|
715
|
-
if not path.exists():
|
|
716
|
-
return {
|
|
717
|
-
"error": f"Tests directory not found: {path}. Please create tests or specify a test path.",
|
|
718
|
-
"type": "test_execution",
|
|
719
|
-
"test_path": str(path),
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
# Run tests
|
|
723
|
-
run_result = await self._run_pytest(path, coverage=coverage)
|
|
724
|
-
|
|
725
|
-
return {"type": "test_execution", "test_path": str(path), "result": run_result}
|
|
726
|
-
|
|
727
|
-
def _generate_test_template(
|
|
728
|
-
self,
|
|
729
|
-
file_path: Path,
|
|
730
|
-
code_analysis: dict[str, Any],
|
|
731
|
-
test_framework: str,
|
|
732
|
-
expert_guidance: str,
|
|
733
|
-
integration: bool = False,
|
|
734
|
-
) -> str:
|
|
735
|
-
"""
|
|
736
|
-
Generate a basic test file template.
|
|
737
|
-
|
|
738
|
-
This is a fallback when full LLM generation is not available.
|
|
739
|
-
Creates a structured test file with imports and basic test structure.
|
|
740
|
-
|
|
741
|
-
Args:
|
|
742
|
-
file_path: Source file path
|
|
743
|
-
code_analysis: Code analysis from test_generator
|
|
744
|
-
test_framework: Test framework to use (pytest/unittest)
|
|
745
|
-
expert_guidance: Expert guidance for test generation
|
|
746
|
-
integration: If True, generate integration test template
|
|
747
|
-
|
|
748
|
-
Returns:
|
|
749
|
-
Test file content as string
|
|
750
|
-
"""
|
|
751
|
-
# Determine module import path
|
|
752
|
-
module_path = None
|
|
753
|
-
try:
|
|
754
|
-
# Try to make path relative to project root
|
|
755
|
-
project_root = None
|
|
756
|
-
if hasattr(self, "_project_root") and self._project_root is not None:
|
|
757
|
-
project_root = self._project_root
|
|
758
|
-
else:
|
|
759
|
-
project_root = Path.cwd()
|
|
760
|
-
|
|
761
|
-
abs_file_path = file_path.resolve()
|
|
762
|
-
abs_project_root = project_root.resolve()
|
|
763
|
-
|
|
764
|
-
# Calculate relative path
|
|
765
|
-
try:
|
|
766
|
-
rel_path = abs_file_path.relative_to(abs_project_root)
|
|
767
|
-
# Convert to module path (remove .py, replace / with .)
|
|
768
|
-
module_path = str(rel_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
769
|
-
except ValueError:
|
|
770
|
-
# File is not under project root, try parent directory
|
|
771
|
-
# This can happen with worktrees or symlinks
|
|
772
|
-
parent = abs_file_path.parent
|
|
773
|
-
if parent != abs_file_path: # Avoid infinite loop
|
|
774
|
-
try:
|
|
775
|
-
rel_path = parent.relative_to(abs_project_root) / abs_file_path.name
|
|
776
|
-
module_path = str(rel_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
777
|
-
except ValueError:
|
|
778
|
-
# Fall through to filename fallback
|
|
779
|
-
module_path = None
|
|
780
|
-
except (AttributeError, OSError) as e:
|
|
781
|
-
logger.debug(f"Error calculating module path: {e}")
|
|
782
|
-
module_path = None
|
|
783
|
-
|
|
784
|
-
# Fallback to filename if module path calculation failed
|
|
785
|
-
if not module_path:
|
|
786
|
-
module_path = file_path.stem
|
|
787
|
-
|
|
788
|
-
if test_framework == "pytest":
|
|
789
|
-
lines = [
|
|
790
|
-
'"""',
|
|
791
|
-
f"Tests for {file_path.name}",
|
|
792
|
-
'"""',
|
|
793
|
-
"",
|
|
794
|
-
"import pytest",
|
|
795
|
-
"",
|
|
796
|
-
]
|
|
797
|
-
|
|
798
|
-
# Add import for the module being tested
|
|
799
|
-
lines.append(f"# Import module under test")
|
|
800
|
-
lines.append(f"# NOTE: If this import fails, adjust the path based on your project structure")
|
|
801
|
-
lines.append(f"# Common fixes:")
|
|
802
|
-
lines.append(f"# - If src/ is not a package, remove 'src.' prefix: from {module_path.replace('src.', '', 1) if module_path.startswith('src.') else module_path} import *")
|
|
803
|
-
lines.append(f"# - Use explicit imports: from {module_path} import ClassName, function_name")
|
|
804
|
-
lines.append(f"from {module_path} import *")
|
|
805
|
-
lines.append("")
|
|
806
|
-
|
|
807
|
-
if expert_guidance:
|
|
808
|
-
lines.append("# Expert Guidance:")
|
|
809
|
-
lines.append(f"# {expert_guidance[:200]}...")
|
|
810
|
-
lines.append("")
|
|
811
|
-
|
|
812
|
-
# Add test functions based on code analysis
|
|
813
|
-
functions = code_analysis.get("functions", [])
|
|
814
|
-
classes = code_analysis.get("classes", [])
|
|
815
|
-
|
|
816
|
-
if functions:
|
|
817
|
-
lines.append("# Test functions")
|
|
818
|
-
for func in functions[:5]: # Limit to first 5 functions
|
|
819
|
-
func_name = func.get("name", "unknown")
|
|
820
|
-
lines.append("")
|
|
821
|
-
lines.append(f"def test_{func_name}():")
|
|
822
|
-
lines.append(f' """Test {func_name} function."""')
|
|
823
|
-
lines.append(" # TODO: Implement test")
|
|
824
|
-
lines.append(" pass")
|
|
825
|
-
|
|
826
|
-
if classes:
|
|
827
|
-
lines.append("")
|
|
828
|
-
lines.append("# Test classes")
|
|
829
|
-
for cls in classes[:3]: # Limit to first 3 classes
|
|
830
|
-
cls_name = cls.get("name", "Unknown")
|
|
831
|
-
methods = cls.get("methods", [])
|
|
832
|
-
lines.append("")
|
|
833
|
-
lines.append(f"class Test{cls_name}:")
|
|
834
|
-
lines.append(f' """Test {cls_name} class."""')
|
|
835
|
-
lines.append("")
|
|
836
|
-
if methods:
|
|
837
|
-
for method in methods[:3]: # Limit to first 3 methods
|
|
838
|
-
lines.append(f" def test_{method}(self):")
|
|
839
|
-
lines.append(f' """Test {method} method."""')
|
|
840
|
-
lines.append(" # TODO: Implement test")
|
|
841
|
-
lines.append(" pass")
|
|
842
|
-
else:
|
|
843
|
-
lines.append(" def test_init(self):")
|
|
844
|
-
lines.append(' """Test initialization."""')
|
|
845
|
-
lines.append(" # TODO: Implement test")
|
|
846
|
-
lines.append(" pass")
|
|
847
|
-
|
|
848
|
-
if not functions and not classes:
|
|
849
|
-
# Generic test structure
|
|
850
|
-
lines.append("")
|
|
851
|
-
lines.append("def test_basic():")
|
|
852
|
-
lines.append(' """Basic test placeholder."""')
|
|
853
|
-
lines.append(" # TODO: Implement tests based on code structure")
|
|
854
|
-
lines.append(" assert True")
|
|
855
|
-
|
|
856
|
-
return "\n".join(lines)
|
|
857
|
-
else:
|
|
858
|
-
# unittest framework
|
|
859
|
-
lines = [
|
|
860
|
-
'"""',
|
|
861
|
-
f"Tests for {file_path.name}",
|
|
862
|
-
'"""',
|
|
863
|
-
"",
|
|
864
|
-
"import unittest",
|
|
865
|
-
"",
|
|
866
|
-
f"# Import module under test",
|
|
867
|
-
f"# NOTE: If this import fails, adjust the path based on your project structure",
|
|
868
|
-
f"# Common fixes:",
|
|
869
|
-
f"# - If src/ is not a package, remove 'src.' prefix: from {module_path.replace('src.', '', 1) if module_path.startswith('src.') else module_path} import *",
|
|
870
|
-
f"# - Use explicit imports: from {module_path} import ClassName, function_name",
|
|
871
|
-
f"from {module_path} import *",
|
|
872
|
-
"",
|
|
873
|
-
]
|
|
874
|
-
|
|
875
|
-
if expert_guidance:
|
|
876
|
-
lines.append("# Expert Guidance:")
|
|
877
|
-
lines.append(f"# {expert_guidance[:200]}...")
|
|
878
|
-
lines.append("")
|
|
879
|
-
|
|
880
|
-
lines.append("")
|
|
881
|
-
lines.append("class TestModule(unittest.TestCase):")
|
|
882
|
-
lines.append(f' """Test cases for {file_path.name}."""')
|
|
883
|
-
lines.append("")
|
|
884
|
-
lines.append(" def setUp(self):")
|
|
885
|
-
lines.append(' """Set up test fixtures."""')
|
|
886
|
-
lines.append(" pass")
|
|
887
|
-
lines.append("")
|
|
888
|
-
lines.append(" def tearDown(self):")
|
|
889
|
-
lines.append(' """Clean up after tests."""')
|
|
890
|
-
lines.append(" pass")
|
|
891
|
-
lines.append("")
|
|
892
|
-
lines.append(" def test_basic(self):")
|
|
893
|
-
lines.append(' """Basic test placeholder."""')
|
|
894
|
-
lines.append(" # TODO: Implement tests based on code structure")
|
|
895
|
-
lines.append(" self.assertTrue(True)")
|
|
896
|
-
|
|
897
|
-
return "\n".join(lines)
|
|
898
|
-
|
|
899
|
-
def _get_test_file_path(self, source_path: Path) -> Path:
|
|
900
|
-
"""Generate test file path from source file path."""
|
|
901
|
-
# Convert source path to absolute and try to make it relative to cwd
|
|
902
|
-
abs_source = source_path.resolve()
|
|
903
|
-
try:
|
|
904
|
-
cwd = Path.cwd().resolve()
|
|
905
|
-
rel_path = abs_source.relative_to(cwd)
|
|
906
|
-
except ValueError:
|
|
907
|
-
# If not relative to cwd, use the source path's parent structure
|
|
908
|
-
rel_path = source_path
|
|
909
|
-
|
|
910
|
-
# Determine test directory structure
|
|
911
|
-
# If source is in src/, put test in tests/
|
|
912
|
-
# Otherwise mirror directory structure in tests/
|
|
913
|
-
parts = list(rel_path.parts)
|
|
914
|
-
|
|
915
|
-
# Remove filename for directory structure
|
|
916
|
-
file_name = parts[-1]
|
|
917
|
-
|
|
918
|
-
if "src" in parts:
|
|
919
|
-
# Source is in src/, test goes to tests/
|
|
920
|
-
# Replace src with tests
|
|
921
|
-
test_parts = []
|
|
922
|
-
for p in parts[:-1]:
|
|
923
|
-
if p == "src":
|
|
924
|
-
test_parts.append("tests")
|
|
925
|
-
else:
|
|
926
|
-
test_parts.append(p)
|
|
927
|
-
else:
|
|
928
|
-
# Mirror structure in tests/ directory
|
|
929
|
-
test_parts = ["tests"] + parts[:-1]
|
|
930
|
-
|
|
931
|
-
# Generate test file name: test_<original_name>
|
|
932
|
-
test_name = f"test_{Path(file_name).stem}.py"
|
|
933
|
-
|
|
934
|
-
# Build path relative to cwd
|
|
935
|
-
if test_parts:
|
|
936
|
-
return (Path.cwd() / Path(*test_parts) / test_name).resolve()
|
|
937
|
-
else:
|
|
938
|
-
return (Path.cwd() / "tests" / test_name).resolve()
|
|
939
|
-
|
|
940
|
-
async def _run_pytest(
|
|
941
|
-
self,
|
|
942
|
-
test_path: Path | None = None,
|
|
943
|
-
source_paths: list[str] | None = None,
|
|
944
|
-
coverage: bool = True,
|
|
945
|
-
) -> dict[str, Any]:
|
|
946
|
-
"""
|
|
947
|
-
Run pytest and return results.
|
|
948
|
-
|
|
949
|
-
Args:
|
|
950
|
-
test_path: Path to test file or directory
|
|
951
|
-
source_paths: Source paths for coverage calculation
|
|
952
|
-
coverage: Include coverage report
|
|
953
|
-
"""
|
|
954
|
-
# Prefer pytest on PATH, but fall back to module execution (python -m pytest)
|
|
955
|
-
if shutil.which("pytest"):
|
|
956
|
-
cmd: list[str] = ["pytest", "-v"]
|
|
957
|
-
else:
|
|
958
|
-
cmd = [sys.executable, "-m", "pytest", "-v"]
|
|
959
|
-
|
|
960
|
-
# Determine root directory to limit test discovery scope
|
|
961
|
-
# This prevents pytest from discovering tests in parent directories
|
|
962
|
-
rootdir = None
|
|
963
|
-
if hasattr(self, "project_root") and self.project_root:
|
|
964
|
-
rootdir = self.project_root
|
|
965
|
-
elif test_path:
|
|
966
|
-
# Use test_path's parent as rootdir to limit discovery
|
|
967
|
-
rootdir = test_path.resolve().parent
|
|
968
|
-
else:
|
|
969
|
-
# Use current working directory
|
|
970
|
-
rootdir = Path.cwd()
|
|
971
|
-
|
|
972
|
-
# Add rootdir to limit pytest's search scope
|
|
973
|
-
if rootdir and rootdir.exists():
|
|
974
|
-
cmd.extend(["--rootdir", str(rootdir)])
|
|
975
|
-
|
|
976
|
-
# Use parallel execution and unit test marker when running all tests
|
|
977
|
-
# (not when a specific test_path is provided, as it might be integration/e2e)
|
|
978
|
-
if not test_path:
|
|
979
|
-
cmd.extend(["-m", "unit", "-n", "auto"])
|
|
980
|
-
else:
|
|
981
|
-
# Still use parallel execution for specific paths (faster)
|
|
982
|
-
cmd.extend(["-n", "auto"])
|
|
983
|
-
|
|
984
|
-
if coverage and source_paths:
|
|
985
|
-
# Add coverage options
|
|
986
|
-
source_modules = ",".join([str(Path(p).stem) for p in source_paths])
|
|
987
|
-
cmd.extend(
|
|
988
|
-
[
|
|
989
|
-
"--cov",
|
|
990
|
-
source_modules,
|
|
991
|
-
"--cov-report=term-missing",
|
|
992
|
-
"--cov-report=json:coverage.json",
|
|
993
|
-
]
|
|
994
|
-
)
|
|
995
|
-
elif coverage:
|
|
996
|
-
# Coverage for all modules
|
|
997
|
-
cmd.extend(
|
|
998
|
-
[
|
|
999
|
-
"--cov",
|
|
1000
|
-
".",
|
|
1001
|
-
"--cov-report=term-missing",
|
|
1002
|
-
"--cov-report=json:coverage.json",
|
|
1003
|
-
]
|
|
1004
|
-
)
|
|
1005
|
-
|
|
1006
|
-
if test_path:
|
|
1007
|
-
cmd.append(str(test_path))
|
|
1008
|
-
|
|
1009
|
-
try:
|
|
1010
|
-
result = subprocess.run( # nosec B603
|
|
1011
|
-
cmd, capture_output=True, text=True, timeout=300 # 5 minute timeout
|
|
1012
|
-
)
|
|
1013
|
-
|
|
1014
|
-
# Parse coverage if available
|
|
1015
|
-
coverage_data = None
|
|
1016
|
-
if coverage and Path("coverage.json").exists():
|
|
1017
|
-
try:
|
|
1018
|
-
with open("coverage.json", encoding="utf-8") as f:
|
|
1019
|
-
coverage_data = json.load(f)
|
|
1020
|
-
except (json.JSONDecodeError, FileNotFoundError):
|
|
1021
|
-
pass
|
|
1022
|
-
|
|
1023
|
-
# Extract summary from stdout
|
|
1024
|
-
summary_match = re.search(
|
|
1025
|
-
r"(\d+) (passed|failed|error)", result.stdout, re.IGNORECASE
|
|
1026
|
-
)
|
|
1027
|
-
summary = None
|
|
1028
|
-
if summary_match:
|
|
1029
|
-
summary = {
|
|
1030
|
-
"count": int(summary_match.group(1)),
|
|
1031
|
-
"status": summary_match.group(2).lower(),
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
return {
|
|
1035
|
-
"success": result.returncode == 0,
|
|
1036
|
-
"return_code": result.returncode,
|
|
1037
|
-
"stdout": result.stdout,
|
|
1038
|
-
"stderr": result.stderr,
|
|
1039
|
-
"summary": summary,
|
|
1040
|
-
"coverage": coverage_data.get("totals") if coverage_data else None,
|
|
1041
|
-
}
|
|
1042
|
-
except subprocess.TimeoutExpired:
|
|
1043
|
-
return {
|
|
1044
|
-
"success": False,
|
|
1045
|
-
"error": "Test execution timeout (5 minutes)",
|
|
1046
|
-
"return_code": -1,
|
|
1047
|
-
}
|
|
1048
|
-
except Exception as e:
|
|
1049
|
-
return {"success": False, "error": str(e), "return_code": -1}
|
|
1050
|
-
|
|
1051
|
-
def _help(self) -> dict[str, Any]:
|
|
1052
|
-
"""
|
|
1053
|
-
Return help information for Tester Agent.
|
|
1054
|
-
|
|
1055
|
-
Returns standardized help format with commands and usage examples.
|
|
1056
|
-
|
|
1057
|
-
Returns:
|
|
1058
|
-
dict: Help information with standardized format:
|
|
1059
|
-
- type (str): Always "help"
|
|
1060
|
-
- content (str): Formatted help text containing:
|
|
1061
|
-
- Available commands (from format_help())
|
|
1062
|
-
- Usage examples for test, generate-tests, and run-tests commands
|
|
1063
|
-
|
|
1064
|
-
Note:
|
|
1065
|
-
This method is synchronous as it performs no I/O operations.
|
|
1066
|
-
Called via agent.run("help") which handles async context.
|
|
1067
|
-
Uses BaseAgent.format_help() which caches command list for performance.
|
|
1068
|
-
"""
|
|
1069
|
-
# Optimize string building by using list and join
|
|
1070
|
-
examples = [
|
|
1071
|
-
" *test file.py # Generate and run tests for file.py",
|
|
1072
|
-
" *generate-tests file.py # Generate tests only",
|
|
1073
|
-
" *run-tests # Run all tests in tests/",
|
|
1074
|
-
" *run-tests test_file.py # Run specific test file",
|
|
1075
|
-
]
|
|
1076
|
-
help_text = "\n".join([self.format_help(), "\nExamples:", *examples])
|
|
1077
|
-
return {"type": "help", "content": help_text}
|
|
1078
|
-
|
|
1079
|
-
async def close(self):
|
|
1080
|
-
"""Close agent and clean up resources."""
|
|
1
|
+
"""
|
|
2
|
+
Tester Agent - Generates and runs tests
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess # nosec B404
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from ...context7.agent_integration import Context7AgentHelper, get_context7_helper
|
|
15
|
+
from ...core.agent_base import BaseAgent
|
|
16
|
+
from ...core.config import ProjectConfig, load_config
|
|
17
|
+
from ...core.test_generator import TestGenerator as CoreTestGenerator
|
|
18
|
+
from ...experts.agent_integration import ExpertSupportMixin
|
|
19
|
+
from .test_generator import TestGenerator
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TesterAgent(BaseAgent, ExpertSupportMixin):
|
|
25
|
+
"""
|
|
26
|
+
Tester Agent - Test generation and execution.
|
|
27
|
+
|
|
28
|
+
Permissions: Read, Write, Edit, Grep, Glob, Bash
|
|
29
|
+
|
|
30
|
+
⚠️ CRITICAL ACCURACY REQUIREMENT:
|
|
31
|
+
- NEVER make up, invent, or fabricate information - Only report verified facts
|
|
32
|
+
- ALWAYS verify claims by checking actual results, not just test pass/fail
|
|
33
|
+
- Verify API calls succeed - inspect response data, status codes, error messages
|
|
34
|
+
- Distinguish between code paths executing and actual functionality working
|
|
35
|
+
- Admit uncertainty explicitly when you cannot verify
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: ProjectConfig | None = None):
|
|
39
|
+
super().__init__(agent_id="tester", agent_name="Tester Agent", config=config)
|
|
40
|
+
# Use config if provided, otherwise load defaults
|
|
41
|
+
if config is None:
|
|
42
|
+
config = load_config()
|
|
43
|
+
self.config = config
|
|
44
|
+
|
|
45
|
+
# Initialize test generators
|
|
46
|
+
self.test_generator = TestGenerator() # Instruction-based generator
|
|
47
|
+
self.core_test_generator = CoreTestGenerator(project_root=self.project_root if hasattr(self, 'project_root') else None) # Template-based generator
|
|
48
|
+
|
|
49
|
+
# Get tester config
|
|
50
|
+
tester_config = config.agents.tester if config and config.agents else None
|
|
51
|
+
self.test_framework = (
|
|
52
|
+
tester_config.test_framework if tester_config else "pytest"
|
|
53
|
+
)
|
|
54
|
+
self.tests_dir = (
|
|
55
|
+
Path(tester_config.tests_dir)
|
|
56
|
+
if tester_config and tester_config.tests_dir
|
|
57
|
+
else Path("tests")
|
|
58
|
+
)
|
|
59
|
+
self.coverage_threshold = (
|
|
60
|
+
tester_config.coverage_threshold if tester_config else 80.0
|
|
61
|
+
)
|
|
62
|
+
self.auto_write_tests = (
|
|
63
|
+
tester_config.auto_write_tests if tester_config else True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Ensure tests directory exists
|
|
67
|
+
self.tests_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
# Initialize Context7 helper
|
|
70
|
+
self.context7: Context7AgentHelper | None = None
|
|
71
|
+
if config:
|
|
72
|
+
self.context7 = get_context7_helper(self, config)
|
|
73
|
+
|
|
74
|
+
# Expert registry initialization (required due to multiple inheritance MRO issue)
|
|
75
|
+
# BaseAgent.__init__() doesn't call super().__init__(), so ExpertSupportMixin.__init__()
|
|
76
|
+
# is never called via MRO. We must manually initialize to avoid AttributeError.
|
|
77
|
+
# The registry will be properly initialized in activate() via _initialize_expert_support()
|
|
78
|
+
self.expert_registry: Any | None = None
|
|
79
|
+
|
|
80
|
+
async def activate(self, project_root: Path | None = None, offline_mode: bool = False):
|
|
81
|
+
"""Activate the tester agent with expert support."""
|
|
82
|
+
# Validate that expert_registry attribute exists (safety check)
|
|
83
|
+
if not hasattr(self, 'expert_registry'):
|
|
84
|
+
raise AttributeError(
|
|
85
|
+
f"{self.__class__.__name__}.expert_registry not initialized. "
|
|
86
|
+
"This should not happen if __init__() properly initializes the attribute."
|
|
87
|
+
)
|
|
88
|
+
await super().activate(project_root, offline_mode=offline_mode)
|
|
89
|
+
# Initialize expert support
|
|
90
|
+
await self._initialize_expert_support(project_root, offline_mode=offline_mode)
|
|
91
|
+
|
|
92
|
+
def get_commands(self) -> list[dict[str, str]]:
|
|
93
|
+
"""Return list of available commands."""
|
|
94
|
+
commands = super().get_commands()
|
|
95
|
+
commands.extend(
|
|
96
|
+
[
|
|
97
|
+
{
|
|
98
|
+
"command": "*test",
|
|
99
|
+
"description": "Generate and run tests for a file",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"command": "*generate-tests",
|
|
103
|
+
"description": "Generate tests without running",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"command": "*generate-e2e-tests",
|
|
107
|
+
"description": "Generate end-to-end tests (requires E2E framework)",
|
|
108
|
+
},
|
|
109
|
+
{"command": "*run-tests", "description": "Run existing tests"},
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
return commands
|
|
113
|
+
|
|
114
|
+
async def run(self, command: str, **kwargs) -> dict[str, Any]:
|
|
115
|
+
"""Execute a command."""
|
|
116
|
+
if command == "test":
|
|
117
|
+
return await self.test_command(**kwargs)
|
|
118
|
+
elif command == "generate-tests":
|
|
119
|
+
return await self.generate_tests_command(**kwargs)
|
|
120
|
+
elif command == "generate-e2e-tests":
|
|
121
|
+
return await self.generate_e2e_tests_command(**kwargs)
|
|
122
|
+
elif command == "run-tests":
|
|
123
|
+
return await self.run_tests_command(**kwargs)
|
|
124
|
+
elif command == "help":
|
|
125
|
+
return self._help()
|
|
126
|
+
else:
|
|
127
|
+
return {"error": f"Unknown command: {command}"}
|
|
128
|
+
|
|
129
|
+
async def test_command(
|
|
130
|
+
self,
|
|
131
|
+
file: str | None = None,
|
|
132
|
+
test_file: str | None = None,
|
|
133
|
+
integration: bool = False,
|
|
134
|
+
focus: str | None = None,
|
|
135
|
+
**kwargs, # Accept additional kwargs to be more flexible
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Generate and run tests for a file.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
file: Source code file to test
|
|
142
|
+
test_file: Optional path to write test file
|
|
143
|
+
integration: If True, generate integration tests
|
|
144
|
+
focus: Comma-separated list of test aspects to focus on
|
|
145
|
+
"""
|
|
146
|
+
if not file:
|
|
147
|
+
return {"error": "File path required"}
|
|
148
|
+
|
|
149
|
+
file_path = Path(file)
|
|
150
|
+
if not file_path.exists():
|
|
151
|
+
return {"error": f"File not found: {file_path}"}
|
|
152
|
+
|
|
153
|
+
# Validate path (inherited from BaseAgent)
|
|
154
|
+
try:
|
|
155
|
+
self._validate_path(file_path, max_file_size=10 * 1024 * 1024)
|
|
156
|
+
except (FileNotFoundError, ValueError) as e:
|
|
157
|
+
return {"error": str(e)}
|
|
158
|
+
|
|
159
|
+
# Build expert query with focus areas if provided
|
|
160
|
+
focus_text = ""
|
|
161
|
+
if focus:
|
|
162
|
+
focus_areas = [area.strip() for area in focus.split(",")]
|
|
163
|
+
focus_text = f" Focus specifically on: {', '.join(focus_areas)}."
|
|
164
|
+
|
|
165
|
+
# D1: Get Context7 test framework documentation using universal auto-detection
|
|
166
|
+
# Enhancement: Use _auto_fetch_context7_docs() for automatic library detection
|
|
167
|
+
framework_docs = None
|
|
168
|
+
test_framework = "pytest" # Default
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
from ...core.language_detector import LanguageDetector
|
|
172
|
+
detector = LanguageDetector()
|
|
173
|
+
language = detector.detect_language(file_path)
|
|
174
|
+
code_content = file_path.read_text(encoding="utf-8") if file_path.exists() else ""
|
|
175
|
+
test_framework = self.test_generator._detect_test_framework_for_language(
|
|
176
|
+
language=language,
|
|
177
|
+
code=code_content
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Use universal auto-detection hook for Context7 docs
|
|
181
|
+
# This automatically detects libraries from code and test framework name in prompt
|
|
182
|
+
context7_docs = await self._auto_fetch_context7_docs(
|
|
183
|
+
code=code_content,
|
|
184
|
+
prompt=f"Generate tests using {test_framework}",
|
|
185
|
+
language=language,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Extract test framework docs if available
|
|
189
|
+
if context7_docs:
|
|
190
|
+
# First try exact match with detected framework
|
|
191
|
+
if test_framework in context7_docs and context7_docs[test_framework]:
|
|
192
|
+
framework_docs = context7_docs[test_framework]
|
|
193
|
+
else:
|
|
194
|
+
# Look for any test framework in the detected libraries
|
|
195
|
+
test_frameworks = ["pytest", "jest", "vitest", "unittest", "mocha", "jasmine"]
|
|
196
|
+
for lib_name, lib_docs in context7_docs.items():
|
|
197
|
+
if lib_name.lower() in [tf.lower() for tf in test_frameworks] and lib_docs:
|
|
198
|
+
framework_docs = lib_docs
|
|
199
|
+
test_framework = lib_name
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
if framework_docs:
|
|
203
|
+
logger.debug(f"D1: Auto-fetched Context7 docs for {test_framework} using universal hook")
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.debug(f"D1: Context7 auto-detection failed: {e}, continuing without Context7 docs")
|
|
206
|
+
|
|
207
|
+
# Consult Testing expert for test generation guidance
|
|
208
|
+
expert_guidance = ""
|
|
209
|
+
expert_advice = None
|
|
210
|
+
|
|
211
|
+
# Enhance expert query with Context7 best practices if available
|
|
212
|
+
context7_guidance = ""
|
|
213
|
+
if framework_docs and framework_docs.get("content"):
|
|
214
|
+
content_preview = framework_docs.get("content", "")[:1000] # First 1000 chars
|
|
215
|
+
context7_guidance = f"\n\nContext7 Best Practices for {test_framework}:\n{content_preview}"
|
|
216
|
+
|
|
217
|
+
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
218
|
+
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
219
|
+
testing_consultation = await self.expert_registry.consult(
|
|
220
|
+
query=f"Provide best practices for generating {'integration' if integration else 'unit'} tests for: {file_path.name}. Focus on test coverage, edge cases, and maintainability.{focus_text}{context7_guidance}",
|
|
221
|
+
domain="testing-strategies",
|
|
222
|
+
agent_id=self.agent_id,
|
|
223
|
+
prioritize_builtin=True,
|
|
224
|
+
)
|
|
225
|
+
if (
|
|
226
|
+
testing_consultation.confidence
|
|
227
|
+
>= testing_consultation.confidence_threshold
|
|
228
|
+
):
|
|
229
|
+
expert_guidance = testing_consultation.weighted_answer
|
|
230
|
+
expert_advice = {
|
|
231
|
+
"confidence": testing_consultation.confidence,
|
|
232
|
+
"threshold": testing_consultation.confidence_threshold,
|
|
233
|
+
"guidance": expert_guidance,
|
|
234
|
+
}
|
|
235
|
+
else:
|
|
236
|
+
expert_advice = {
|
|
237
|
+
"confidence": testing_consultation.confidence,
|
|
238
|
+
"threshold": testing_consultation.confidence_threshold,
|
|
239
|
+
"guidance": f"Low confidence expert advice: {testing_consultation.weighted_answer}",
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# Prepare test generation instruction
|
|
243
|
+
if integration:
|
|
244
|
+
instruction = self.test_generator.prepare_integration_tests(
|
|
245
|
+
[file_path],
|
|
246
|
+
test_path=Path(test_file) if test_file else None,
|
|
247
|
+
context=None,
|
|
248
|
+
expert_guidance=expert_guidance,
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
instruction = self.test_generator.prepare_unit_tests(
|
|
252
|
+
file_path,
|
|
253
|
+
test_path=Path(test_file) if test_file else None,
|
|
254
|
+
context=None,
|
|
255
|
+
expert_guidance=expert_guidance,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Determine test file path
|
|
259
|
+
if test_file:
|
|
260
|
+
test_path = Path(test_file)
|
|
261
|
+
else:
|
|
262
|
+
# Auto-generate test file path
|
|
263
|
+
test_path = self._get_test_file_path(file_path)
|
|
264
|
+
|
|
265
|
+
# Ensure test directory exists (fix for nested path issues)
|
|
266
|
+
try:
|
|
267
|
+
test_path.parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.warning(
|
|
270
|
+
f"Failed to create test directory {test_path.parent}: {e}. "
|
|
271
|
+
"Test file creation may fail.",
|
|
272
|
+
exc_info=True
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# D2: Prepare test quality validation info (will be used after test generation)
|
|
276
|
+
quality_validation = None
|
|
277
|
+
if self.context7 and framework_docs:
|
|
278
|
+
try:
|
|
279
|
+
# Get quality standards for the test framework
|
|
280
|
+
quality_standards = await self.context7.get_documentation(
|
|
281
|
+
library=test_framework,
|
|
282
|
+
topic="quality-standards",
|
|
283
|
+
use_fuzzy_match=True
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if quality_standards:
|
|
287
|
+
quality_validation = {
|
|
288
|
+
"framework": test_framework,
|
|
289
|
+
"standards_available": True,
|
|
290
|
+
"source": quality_standards.get("source", "unknown"),
|
|
291
|
+
}
|
|
292
|
+
else:
|
|
293
|
+
quality_validation = {
|
|
294
|
+
"framework": test_framework,
|
|
295
|
+
"standards_available": False,
|
|
296
|
+
}
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.debug(f"D2: Quality standards lookup failed: {e}")
|
|
299
|
+
|
|
300
|
+
# Issue 4 Fix: Actually generate and write test file if auto_write_tests is enabled
|
|
301
|
+
test_code = None
|
|
302
|
+
file_written = False
|
|
303
|
+
run_result = None
|
|
304
|
+
|
|
305
|
+
if self.auto_write_tests:
|
|
306
|
+
# Use test_file if provided, otherwise use auto-generated path
|
|
307
|
+
target_test_path = test_path
|
|
308
|
+
|
|
309
|
+
# Build test generation prompt using test_generator
|
|
310
|
+
try:
|
|
311
|
+
source_code = file_path.read_text(encoding="utf-8")
|
|
312
|
+
code_analysis = self.test_generator._analyze_code(source_code, file_path)
|
|
313
|
+
|
|
314
|
+
if integration:
|
|
315
|
+
prompt = self.test_generator._build_integration_test_prompt(
|
|
316
|
+
code=source_code,
|
|
317
|
+
test_path=target_test_path,
|
|
318
|
+
context=None,
|
|
319
|
+
expert_guidance=expert_guidance,
|
|
320
|
+
)
|
|
321
|
+
else:
|
|
322
|
+
prompt = self.test_generator._build_unit_test_prompt(
|
|
323
|
+
code=source_code,
|
|
324
|
+
analysis=code_analysis,
|
|
325
|
+
test_path=target_test_path,
|
|
326
|
+
context=None,
|
|
327
|
+
expert_guidance=expert_guidance,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Generate test code template using core test generator
|
|
331
|
+
test_framework_detected = self.core_test_generator.detect_test_framework(file_path)
|
|
332
|
+
|
|
333
|
+
# Use framework-specific template generation
|
|
334
|
+
if test_framework_detected == "pytest":
|
|
335
|
+
generated_path = self.core_test_generator.generate_pytest_test(
|
|
336
|
+
source_file=file_path,
|
|
337
|
+
test_content=test_code or "", # Will be generated if empty
|
|
338
|
+
output_file=target_test_path,
|
|
339
|
+
)
|
|
340
|
+
elif test_framework_detected == "jest":
|
|
341
|
+
generated_path = self.core_test_generator.generate_jest_test(
|
|
342
|
+
source_file=file_path,
|
|
343
|
+
test_content=test_code or "",
|
|
344
|
+
output_file=target_test_path,
|
|
345
|
+
)
|
|
346
|
+
elif test_framework_detected == "unittest":
|
|
347
|
+
generated_path = self.core_test_generator.generate_unittest_test(
|
|
348
|
+
source_file=file_path,
|
|
349
|
+
test_content=test_code or "",
|
|
350
|
+
output_file=target_test_path,
|
|
351
|
+
)
|
|
352
|
+
else:
|
|
353
|
+
# Fallback to original method
|
|
354
|
+
test_code = self._generate_test_template(
|
|
355
|
+
file_path=file_path,
|
|
356
|
+
code_analysis=code_analysis,
|
|
357
|
+
test_framework=test_framework,
|
|
358
|
+
expert_guidance=expert_guidance,
|
|
359
|
+
integration=integration,
|
|
360
|
+
)
|
|
361
|
+
target_test_path.write_text(test_code, encoding="utf-8")
|
|
362
|
+
generated_path = target_test_path
|
|
363
|
+
|
|
364
|
+
file_written = True
|
|
365
|
+
logger.info(f"Test file written to: {generated_path}")
|
|
366
|
+
target_test_path = generated_path # Update path
|
|
367
|
+
|
|
368
|
+
# Run tests after generating
|
|
369
|
+
run_result = await self._run_pytest(
|
|
370
|
+
test_path=target_test_path,
|
|
371
|
+
source_paths=[str(file_path)],
|
|
372
|
+
coverage=True,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
logger.warning(
|
|
377
|
+
f"Failed to auto-generate test file: {e}. "
|
|
378
|
+
"Returning instruction for manual execution.",
|
|
379
|
+
exc_info=True
|
|
380
|
+
)
|
|
381
|
+
# Fall through to return instruction
|
|
382
|
+
|
|
383
|
+
result = {
|
|
384
|
+
"type": "test",
|
|
385
|
+
"instruction": instruction.to_dict(),
|
|
386
|
+
"skill_command": instruction.to_skill_command(),
|
|
387
|
+
"test_file": str(test_path),
|
|
388
|
+
"test_directory": str(test_path.parent),
|
|
389
|
+
"source_file": str(file_path), # Traceability: source under test
|
|
390
|
+
"file_written": file_written,
|
|
391
|
+
"run_result": run_result,
|
|
392
|
+
"expert_advice": expert_advice, # Include expert recommendations
|
|
393
|
+
# D1 Enhancement: Context7 framework documentation
|
|
394
|
+
"context7_framework_docs": {
|
|
395
|
+
"framework": test_framework,
|
|
396
|
+
"docs_available": framework_docs is not None,
|
|
397
|
+
"source": framework_docs.get("source") if framework_docs else None,
|
|
398
|
+
} if framework_docs else None,
|
|
399
|
+
# D2 Enhancement: Quality validation info
|
|
400
|
+
"quality_validation": quality_validation,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if test_code and file_written:
|
|
404
|
+
result["test_code_preview"] = test_code[:500] # Preview of generated code
|
|
405
|
+
|
|
406
|
+
return result
|
|
407
|
+
|
|
408
|
+
async def generate_tests_command(
|
|
409
|
+
self,
|
|
410
|
+
file: str | None = None,
|
|
411
|
+
test_file: str | None = None,
|
|
412
|
+
integration: bool = False,
|
|
413
|
+
) -> dict[str, Any]:
|
|
414
|
+
"""
|
|
415
|
+
Generate tests without running them.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
file: Source code file to test
|
|
419
|
+
test_file: Optional path to write test file
|
|
420
|
+
integration: If True, generate integration tests
|
|
421
|
+
"""
|
|
422
|
+
if not file:
|
|
423
|
+
return {"error": "File path required"}
|
|
424
|
+
|
|
425
|
+
file_path = Path(file)
|
|
426
|
+
if not file_path.exists():
|
|
427
|
+
return {"error": f"File not found: {file_path}"}
|
|
428
|
+
|
|
429
|
+
# Validate path (inherited from BaseAgent)
|
|
430
|
+
try:
|
|
431
|
+
self._validate_path(file_path, max_file_size=10 * 1024 * 1024)
|
|
432
|
+
except (FileNotFoundError, ValueError) as e:
|
|
433
|
+
return {"error": str(e)}
|
|
434
|
+
|
|
435
|
+
# D1: Get Context7 test framework documentation (same as test_command)
|
|
436
|
+
framework_docs = None
|
|
437
|
+
test_framework = "pytest" # Default
|
|
438
|
+
try:
|
|
439
|
+
from ...core.language_detector import LanguageDetector
|
|
440
|
+
detector = LanguageDetector()
|
|
441
|
+
language = detector.detect_language(file_path)
|
|
442
|
+
test_framework = self.test_generator._detect_test_framework_for_language(
|
|
443
|
+
language=language,
|
|
444
|
+
code=file_path.read_text(encoding="utf-8") if file_path.exists() else ""
|
|
445
|
+
)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.debug(f"Failed to detect test framework: {e}, using default pytest")
|
|
448
|
+
|
|
449
|
+
if self.context7:
|
|
450
|
+
try:
|
|
451
|
+
framework_docs = await self.context7.get_documentation(
|
|
452
|
+
library=test_framework,
|
|
453
|
+
topic="testing",
|
|
454
|
+
use_fuzzy_match=True
|
|
455
|
+
)
|
|
456
|
+
if framework_docs:
|
|
457
|
+
logger.debug(f"D1: Fetched Context7 docs for {test_framework}")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
logger.debug(f"D1: Context7 framework docs lookup failed: {e}")
|
|
460
|
+
|
|
461
|
+
# Consult Testing expert for test generation guidance
|
|
462
|
+
expert_guidance = ""
|
|
463
|
+
context7_guidance = ""
|
|
464
|
+
if framework_docs and framework_docs.get("content"):
|
|
465
|
+
content_preview = framework_docs.get("content", "")[:1000]
|
|
466
|
+
context7_guidance = f"\n\nContext7 Best Practices for {test_framework}:\n{content_preview}"
|
|
467
|
+
|
|
468
|
+
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
469
|
+
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
470
|
+
testing_consultation = await self.expert_registry.consult(
|
|
471
|
+
query=f"Provide best practices for generating {'integration' if integration else 'unit'} tests for: {file_path.name}. Focus on test coverage, edge cases, and maintainability.{context7_guidance}",
|
|
472
|
+
domain="testing-strategies",
|
|
473
|
+
agent_id=self.agent_id,
|
|
474
|
+
prioritize_builtin=True,
|
|
475
|
+
)
|
|
476
|
+
if (
|
|
477
|
+
testing_consultation.confidence
|
|
478
|
+
>= testing_consultation.confidence_threshold
|
|
479
|
+
):
|
|
480
|
+
expert_guidance = testing_consultation.weighted_answer
|
|
481
|
+
|
|
482
|
+
# Prepare test generation instruction
|
|
483
|
+
if integration:
|
|
484
|
+
instruction = self.test_generator.prepare_integration_tests(
|
|
485
|
+
[file_path],
|
|
486
|
+
test_path=Path(test_file) if test_file else None,
|
|
487
|
+
context=None,
|
|
488
|
+
expert_guidance=expert_guidance,
|
|
489
|
+
)
|
|
490
|
+
else:
|
|
491
|
+
instruction = self.test_generator.prepare_unit_tests(
|
|
492
|
+
file_path,
|
|
493
|
+
test_path=Path(test_file) if test_file else None,
|
|
494
|
+
context=None,
|
|
495
|
+
expert_guidance=expert_guidance,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# Determine test file path
|
|
499
|
+
if test_file:
|
|
500
|
+
test_path = Path(test_file)
|
|
501
|
+
else:
|
|
502
|
+
test_path = self._get_test_file_path(file_path)
|
|
503
|
+
|
|
504
|
+
# Ensure test directory exists
|
|
505
|
+
try:
|
|
506
|
+
test_path.parent.mkdir(parents=True, exist_ok=True)
|
|
507
|
+
except Exception as e:
|
|
508
|
+
logger.warning(
|
|
509
|
+
f"Failed to create test directory {test_path.parent}: {e}. "
|
|
510
|
+
"Test file creation may fail.",
|
|
511
|
+
exc_info=True
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# D2: Quality validation info
|
|
515
|
+
quality_validation = None
|
|
516
|
+
if self.context7 and framework_docs:
|
|
517
|
+
try:
|
|
518
|
+
quality_standards = await self.context7.get_documentation(
|
|
519
|
+
library=test_framework,
|
|
520
|
+
topic="quality-standards",
|
|
521
|
+
use_fuzzy_match=True
|
|
522
|
+
)
|
|
523
|
+
if quality_standards:
|
|
524
|
+
quality_validation = {
|
|
525
|
+
"framework": test_framework,
|
|
526
|
+
"standards_available": True,
|
|
527
|
+
"source": quality_standards.get("source", "unknown"),
|
|
528
|
+
}
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.debug(f"D2: Quality standards lookup failed: {e}")
|
|
531
|
+
|
|
532
|
+
# Issue 4 Fix: Actually generate and write test file if auto_write_tests is enabled
|
|
533
|
+
test_code = None
|
|
534
|
+
file_written = False
|
|
535
|
+
if self.auto_write_tests:
|
|
536
|
+
# Build test generation prompt using test_generator
|
|
537
|
+
try:
|
|
538
|
+
source_code = file_path.read_text(encoding="utf-8")
|
|
539
|
+
code_analysis = self.test_generator._analyze_code(source_code, file_path)
|
|
540
|
+
|
|
541
|
+
if integration:
|
|
542
|
+
prompt = self.test_generator._build_integration_test_prompt(
|
|
543
|
+
code=source_code,
|
|
544
|
+
test_path=test_path,
|
|
545
|
+
context=None,
|
|
546
|
+
expert_guidance=expert_guidance,
|
|
547
|
+
)
|
|
548
|
+
else:
|
|
549
|
+
prompt = self.test_generator._build_unit_test_prompt(
|
|
550
|
+
code=source_code,
|
|
551
|
+
analysis=code_analysis,
|
|
552
|
+
test_path=test_path,
|
|
553
|
+
context=None,
|
|
554
|
+
expert_guidance=expert_guidance,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Use implementer agent to generate test code
|
|
558
|
+
from ...agents.implementer.agent import ImplementerAgent
|
|
559
|
+
implementer = ImplementerAgent(config=self.config)
|
|
560
|
+
await implementer.activate(offline_mode=False)
|
|
561
|
+
|
|
562
|
+
# Generate test code using implementer's generate_code
|
|
563
|
+
generate_result = await implementer.generate_code(
|
|
564
|
+
specification=prompt,
|
|
565
|
+
file_path=str(test_path),
|
|
566
|
+
context=source_code,
|
|
567
|
+
language="python",
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Extract generated code from result
|
|
571
|
+
# Note: implementer.generate_code returns instruction, but we can use the prompt
|
|
572
|
+
# to generate code via Cursor AI if available, or create a template
|
|
573
|
+
# For now, create a basic test file template based on the analysis
|
|
574
|
+
test_code = self._generate_test_template(
|
|
575
|
+
file_path=file_path,
|
|
576
|
+
code_analysis=code_analysis,
|
|
577
|
+
test_framework=test_framework,
|
|
578
|
+
expert_guidance=expert_guidance,
|
|
579
|
+
integration=integration,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Write test file
|
|
583
|
+
test_path.write_text(test_code, encoding="utf-8")
|
|
584
|
+
file_written = True
|
|
585
|
+
logger.info(f"Test file written to: {test_path}")
|
|
586
|
+
|
|
587
|
+
await implementer.close()
|
|
588
|
+
except Exception as e:
|
|
589
|
+
logger.warning(
|
|
590
|
+
f"Failed to auto-generate test file: {e}. "
|
|
591
|
+
"Returning instruction for manual execution.",
|
|
592
|
+
exc_info=True
|
|
593
|
+
)
|
|
594
|
+
# Fall through to return instruction
|
|
595
|
+
|
|
596
|
+
result = {
|
|
597
|
+
"type": "test_generation",
|
|
598
|
+
"instruction": instruction.to_dict(),
|
|
599
|
+
"skill_command": instruction.to_skill_command(),
|
|
600
|
+
"test_file": str(test_path),
|
|
601
|
+
"source_file": str(file_path), # Traceability: source under test
|
|
602
|
+
"file_written": file_written,
|
|
603
|
+
# D1 Enhancement
|
|
604
|
+
"context7_framework_docs": {
|
|
605
|
+
"framework": test_framework,
|
|
606
|
+
"docs_available": framework_docs is not None,
|
|
607
|
+
"source": framework_docs.get("source") if framework_docs else None,
|
|
608
|
+
} if framework_docs else None,
|
|
609
|
+
# D2 Enhancement
|
|
610
|
+
"quality_validation": quality_validation,
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if test_code and file_written:
|
|
614
|
+
result["test_code_preview"] = test_code[:500] # Preview of generated code
|
|
615
|
+
|
|
616
|
+
return result
|
|
617
|
+
|
|
618
|
+
async def generate_e2e_tests_command(
|
|
619
|
+
self,
|
|
620
|
+
test_file: str | None = None,
|
|
621
|
+
project_root: str | None = None,
|
|
622
|
+
) -> dict[str, Any]:
|
|
623
|
+
"""
|
|
624
|
+
Generate end-to-end (E2E) tests for the project.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
test_file: Optional path where test will be written
|
|
628
|
+
project_root: Optional project root directory (default: current directory)
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Dictionary with test generation results
|
|
632
|
+
"""
|
|
633
|
+
# Determine project root
|
|
634
|
+
if project_root:
|
|
635
|
+
proj_root = Path(project_root)
|
|
636
|
+
else:
|
|
637
|
+
proj_root = self.project_root if hasattr(self, "project_root") else Path.cwd()
|
|
638
|
+
|
|
639
|
+
if not proj_root.exists():
|
|
640
|
+
return {"error": f"Project root not found: {proj_root}"}
|
|
641
|
+
|
|
642
|
+
# Consult Testing expert for E2E test generation guidance
|
|
643
|
+
expert_guidance = ""
|
|
644
|
+
expert_advice = None
|
|
645
|
+
# Use defensive check to ensure attribute exists (safety for MRO issue)
|
|
646
|
+
if hasattr(self, 'expert_registry') and self.expert_registry:
|
|
647
|
+
testing_consultation = await self.expert_registry.consult(
|
|
648
|
+
query="Provide best practices for generating end-to-end (E2E) tests. Focus on test coverage, user workflows, and maintainability.",
|
|
649
|
+
domain="testing-strategies",
|
|
650
|
+
agent_id=self.agent_id,
|
|
651
|
+
prioritize_builtin=True,
|
|
652
|
+
)
|
|
653
|
+
if (
|
|
654
|
+
testing_consultation.confidence
|
|
655
|
+
>= testing_consultation.confidence_threshold
|
|
656
|
+
):
|
|
657
|
+
expert_guidance = testing_consultation.weighted_answer
|
|
658
|
+
expert_advice = {
|
|
659
|
+
"confidence": testing_consultation.confidence,
|
|
660
|
+
"threshold": testing_consultation.confidence_threshold,
|
|
661
|
+
"guidance": expert_guidance,
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
# Prepare E2E test generation instruction
|
|
665
|
+
instruction = self.test_generator.prepare_e2e_tests(
|
|
666
|
+
project_root=proj_root,
|
|
667
|
+
test_path=Path(test_file) if test_file else None,
|
|
668
|
+
context=None,
|
|
669
|
+
expert_guidance=expert_guidance,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
# Check if E2E framework was detected
|
|
673
|
+
if instruction is None:
|
|
674
|
+
return {
|
|
675
|
+
"type": "e2e_test_generation",
|
|
676
|
+
"error": "No E2E testing framework detected. Please install one of: playwright, pytest-playwright, selenium, or cypress.",
|
|
677
|
+
"instruction": None,
|
|
678
|
+
"test_file": None,
|
|
679
|
+
"framework_detected": False,
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
# Determine test file path
|
|
683
|
+
if test_file:
|
|
684
|
+
test_path = Path(test_file)
|
|
685
|
+
else:
|
|
686
|
+
# Default E2E test location
|
|
687
|
+
test_path = proj_root / "tests" / "e2e" / "test_e2e.py"
|
|
688
|
+
|
|
689
|
+
return {
|
|
690
|
+
"type": "e2e_test_generation",
|
|
691
|
+
"instruction": instruction.to_dict(),
|
|
692
|
+
"skill_command": instruction.to_skill_command(),
|
|
693
|
+
"test_file": str(test_path),
|
|
694
|
+
"framework_detected": instruction.test_framework,
|
|
695
|
+
"expert_advice": expert_advice,
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async def run_tests_command(
|
|
699
|
+
self, test_path: str | None = None, coverage: bool = True
|
|
700
|
+
) -> dict[str, Any]:
|
|
701
|
+
"""
|
|
702
|
+
Run existing tests.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
test_path: Path to test file or directory (default: tests/)
|
|
706
|
+
coverage: Include coverage report
|
|
707
|
+
"""
|
|
708
|
+
if test_path:
|
|
709
|
+
path = Path(test_path)
|
|
710
|
+
if not path.exists():
|
|
711
|
+
return {"error": f"Test path not found: {path}"}
|
|
712
|
+
else:
|
|
713
|
+
path = self.tests_dir
|
|
714
|
+
# Check if tests directory exists
|
|
715
|
+
if not path.exists():
|
|
716
|
+
return {
|
|
717
|
+
"error": f"Tests directory not found: {path}. Please create tests or specify a test path.",
|
|
718
|
+
"type": "test_execution",
|
|
719
|
+
"test_path": str(path),
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
# Run tests
|
|
723
|
+
run_result = await self._run_pytest(path, coverage=coverage)
|
|
724
|
+
|
|
725
|
+
return {"type": "test_execution", "test_path": str(path), "result": run_result}
|
|
726
|
+
|
|
727
|
+
def _generate_test_template(
|
|
728
|
+
self,
|
|
729
|
+
file_path: Path,
|
|
730
|
+
code_analysis: dict[str, Any],
|
|
731
|
+
test_framework: str,
|
|
732
|
+
expert_guidance: str,
|
|
733
|
+
integration: bool = False,
|
|
734
|
+
) -> str:
|
|
735
|
+
"""
|
|
736
|
+
Generate a basic test file template.
|
|
737
|
+
|
|
738
|
+
This is a fallback when full LLM generation is not available.
|
|
739
|
+
Creates a structured test file with imports and basic test structure.
|
|
740
|
+
|
|
741
|
+
Args:
|
|
742
|
+
file_path: Source file path
|
|
743
|
+
code_analysis: Code analysis from test_generator
|
|
744
|
+
test_framework: Test framework to use (pytest/unittest)
|
|
745
|
+
expert_guidance: Expert guidance for test generation
|
|
746
|
+
integration: If True, generate integration test template
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
Test file content as string
|
|
750
|
+
"""
|
|
751
|
+
# Determine module import path
|
|
752
|
+
module_path = None
|
|
753
|
+
try:
|
|
754
|
+
# Try to make path relative to project root
|
|
755
|
+
project_root = None
|
|
756
|
+
if hasattr(self, "_project_root") and self._project_root is not None:
|
|
757
|
+
project_root = self._project_root
|
|
758
|
+
else:
|
|
759
|
+
project_root = Path.cwd()
|
|
760
|
+
|
|
761
|
+
abs_file_path = file_path.resolve()
|
|
762
|
+
abs_project_root = project_root.resolve()
|
|
763
|
+
|
|
764
|
+
# Calculate relative path
|
|
765
|
+
try:
|
|
766
|
+
rel_path = abs_file_path.relative_to(abs_project_root)
|
|
767
|
+
# Convert to module path (remove .py, replace / with .)
|
|
768
|
+
module_path = str(rel_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
769
|
+
except ValueError:
|
|
770
|
+
# File is not under project root, try parent directory
|
|
771
|
+
# This can happen with worktrees or symlinks
|
|
772
|
+
parent = abs_file_path.parent
|
|
773
|
+
if parent != abs_file_path: # Avoid infinite loop
|
|
774
|
+
try:
|
|
775
|
+
rel_path = parent.relative_to(abs_project_root) / abs_file_path.name
|
|
776
|
+
module_path = str(rel_path.with_suffix("")).replace("/", ".").replace("\\", ".")
|
|
777
|
+
except ValueError:
|
|
778
|
+
# Fall through to filename fallback
|
|
779
|
+
module_path = None
|
|
780
|
+
except (AttributeError, OSError) as e:
|
|
781
|
+
logger.debug(f"Error calculating module path: {e}")
|
|
782
|
+
module_path = None
|
|
783
|
+
|
|
784
|
+
# Fallback to filename if module path calculation failed
|
|
785
|
+
if not module_path:
|
|
786
|
+
module_path = file_path.stem
|
|
787
|
+
|
|
788
|
+
if test_framework == "pytest":
|
|
789
|
+
lines = [
|
|
790
|
+
'"""',
|
|
791
|
+
f"Tests for {file_path.name}",
|
|
792
|
+
'"""',
|
|
793
|
+
"",
|
|
794
|
+
"import pytest",
|
|
795
|
+
"",
|
|
796
|
+
]
|
|
797
|
+
|
|
798
|
+
# Add import for the module being tested
|
|
799
|
+
lines.append(f"# Import module under test")
|
|
800
|
+
lines.append(f"# NOTE: If this import fails, adjust the path based on your project structure")
|
|
801
|
+
lines.append(f"# Common fixes:")
|
|
802
|
+
lines.append(f"# - If src/ is not a package, remove 'src.' prefix: from {module_path.replace('src.', '', 1) if module_path.startswith('src.') else module_path} import *")
|
|
803
|
+
lines.append(f"# - Use explicit imports: from {module_path} import ClassName, function_name")
|
|
804
|
+
lines.append(f"from {module_path} import *")
|
|
805
|
+
lines.append("")
|
|
806
|
+
|
|
807
|
+
if expert_guidance:
|
|
808
|
+
lines.append("# Expert Guidance:")
|
|
809
|
+
lines.append(f"# {expert_guidance[:200]}...")
|
|
810
|
+
lines.append("")
|
|
811
|
+
|
|
812
|
+
# Add test functions based on code analysis
|
|
813
|
+
functions = code_analysis.get("functions", [])
|
|
814
|
+
classes = code_analysis.get("classes", [])
|
|
815
|
+
|
|
816
|
+
if functions:
|
|
817
|
+
lines.append("# Test functions")
|
|
818
|
+
for func in functions[:5]: # Limit to first 5 functions
|
|
819
|
+
func_name = func.get("name", "unknown")
|
|
820
|
+
lines.append("")
|
|
821
|
+
lines.append(f"def test_{func_name}():")
|
|
822
|
+
lines.append(f' """Test {func_name} function."""')
|
|
823
|
+
lines.append(" # TODO: Implement test")
|
|
824
|
+
lines.append(" pass")
|
|
825
|
+
|
|
826
|
+
if classes:
|
|
827
|
+
lines.append("")
|
|
828
|
+
lines.append("# Test classes")
|
|
829
|
+
for cls in classes[:3]: # Limit to first 3 classes
|
|
830
|
+
cls_name = cls.get("name", "Unknown")
|
|
831
|
+
methods = cls.get("methods", [])
|
|
832
|
+
lines.append("")
|
|
833
|
+
lines.append(f"class Test{cls_name}:")
|
|
834
|
+
lines.append(f' """Test {cls_name} class."""')
|
|
835
|
+
lines.append("")
|
|
836
|
+
if methods:
|
|
837
|
+
for method in methods[:3]: # Limit to first 3 methods
|
|
838
|
+
lines.append(f" def test_{method}(self):")
|
|
839
|
+
lines.append(f' """Test {method} method."""')
|
|
840
|
+
lines.append(" # TODO: Implement test")
|
|
841
|
+
lines.append(" pass")
|
|
842
|
+
else:
|
|
843
|
+
lines.append(" def test_init(self):")
|
|
844
|
+
lines.append(' """Test initialization."""')
|
|
845
|
+
lines.append(" # TODO: Implement test")
|
|
846
|
+
lines.append(" pass")
|
|
847
|
+
|
|
848
|
+
if not functions and not classes:
|
|
849
|
+
# Generic test structure
|
|
850
|
+
lines.append("")
|
|
851
|
+
lines.append("def test_basic():")
|
|
852
|
+
lines.append(' """Basic test placeholder."""')
|
|
853
|
+
lines.append(" # TODO: Implement tests based on code structure")
|
|
854
|
+
lines.append(" assert True")
|
|
855
|
+
|
|
856
|
+
return "\n".join(lines)
|
|
857
|
+
else:
|
|
858
|
+
# unittest framework
|
|
859
|
+
lines = [
|
|
860
|
+
'"""',
|
|
861
|
+
f"Tests for {file_path.name}",
|
|
862
|
+
'"""',
|
|
863
|
+
"",
|
|
864
|
+
"import unittest",
|
|
865
|
+
"",
|
|
866
|
+
f"# Import module under test",
|
|
867
|
+
f"# NOTE: If this import fails, adjust the path based on your project structure",
|
|
868
|
+
f"# Common fixes:",
|
|
869
|
+
f"# - If src/ is not a package, remove 'src.' prefix: from {module_path.replace('src.', '', 1) if module_path.startswith('src.') else module_path} import *",
|
|
870
|
+
f"# - Use explicit imports: from {module_path} import ClassName, function_name",
|
|
871
|
+
f"from {module_path} import *",
|
|
872
|
+
"",
|
|
873
|
+
]
|
|
874
|
+
|
|
875
|
+
if expert_guidance:
|
|
876
|
+
lines.append("# Expert Guidance:")
|
|
877
|
+
lines.append(f"# {expert_guidance[:200]}...")
|
|
878
|
+
lines.append("")
|
|
879
|
+
|
|
880
|
+
lines.append("")
|
|
881
|
+
lines.append("class TestModule(unittest.TestCase):")
|
|
882
|
+
lines.append(f' """Test cases for {file_path.name}."""')
|
|
883
|
+
lines.append("")
|
|
884
|
+
lines.append(" def setUp(self):")
|
|
885
|
+
lines.append(' """Set up test fixtures."""')
|
|
886
|
+
lines.append(" pass")
|
|
887
|
+
lines.append("")
|
|
888
|
+
lines.append(" def tearDown(self):")
|
|
889
|
+
lines.append(' """Clean up after tests."""')
|
|
890
|
+
lines.append(" pass")
|
|
891
|
+
lines.append("")
|
|
892
|
+
lines.append(" def test_basic(self):")
|
|
893
|
+
lines.append(' """Basic test placeholder."""')
|
|
894
|
+
lines.append(" # TODO: Implement tests based on code structure")
|
|
895
|
+
lines.append(" self.assertTrue(True)")
|
|
896
|
+
|
|
897
|
+
return "\n".join(lines)
|
|
898
|
+
|
|
899
|
+
def _get_test_file_path(self, source_path: Path) -> Path:
|
|
900
|
+
"""Generate test file path from source file path."""
|
|
901
|
+
# Convert source path to absolute and try to make it relative to cwd
|
|
902
|
+
abs_source = source_path.resolve()
|
|
903
|
+
try:
|
|
904
|
+
cwd = Path.cwd().resolve()
|
|
905
|
+
rel_path = abs_source.relative_to(cwd)
|
|
906
|
+
except ValueError:
|
|
907
|
+
# If not relative to cwd, use the source path's parent structure
|
|
908
|
+
rel_path = source_path
|
|
909
|
+
|
|
910
|
+
# Determine test directory structure
|
|
911
|
+
# If source is in src/, put test in tests/
|
|
912
|
+
# Otherwise mirror directory structure in tests/
|
|
913
|
+
parts = list(rel_path.parts)
|
|
914
|
+
|
|
915
|
+
# Remove filename for directory structure
|
|
916
|
+
file_name = parts[-1]
|
|
917
|
+
|
|
918
|
+
if "src" in parts:
|
|
919
|
+
# Source is in src/, test goes to tests/
|
|
920
|
+
# Replace src with tests
|
|
921
|
+
test_parts = []
|
|
922
|
+
for p in parts[:-1]:
|
|
923
|
+
if p == "src":
|
|
924
|
+
test_parts.append("tests")
|
|
925
|
+
else:
|
|
926
|
+
test_parts.append(p)
|
|
927
|
+
else:
|
|
928
|
+
# Mirror structure in tests/ directory
|
|
929
|
+
test_parts = ["tests"] + parts[:-1]
|
|
930
|
+
|
|
931
|
+
# Generate test file name: test_<original_name>
|
|
932
|
+
test_name = f"test_{Path(file_name).stem}.py"
|
|
933
|
+
|
|
934
|
+
# Build path relative to cwd
|
|
935
|
+
if test_parts:
|
|
936
|
+
return (Path.cwd() / Path(*test_parts) / test_name).resolve()
|
|
937
|
+
else:
|
|
938
|
+
return (Path.cwd() / "tests" / test_name).resolve()
|
|
939
|
+
|
|
940
|
+
async def _run_pytest(
|
|
941
|
+
self,
|
|
942
|
+
test_path: Path | None = None,
|
|
943
|
+
source_paths: list[str] | None = None,
|
|
944
|
+
coverage: bool = True,
|
|
945
|
+
) -> dict[str, Any]:
|
|
946
|
+
"""
|
|
947
|
+
Run pytest and return results.
|
|
948
|
+
|
|
949
|
+
Args:
|
|
950
|
+
test_path: Path to test file or directory
|
|
951
|
+
source_paths: Source paths for coverage calculation
|
|
952
|
+
coverage: Include coverage report
|
|
953
|
+
"""
|
|
954
|
+
# Prefer pytest on PATH, but fall back to module execution (python -m pytest)
|
|
955
|
+
if shutil.which("pytest"):
|
|
956
|
+
cmd: list[str] = ["pytest", "-v"]
|
|
957
|
+
else:
|
|
958
|
+
cmd = [sys.executable, "-m", "pytest", "-v"]
|
|
959
|
+
|
|
960
|
+
# Determine root directory to limit test discovery scope
|
|
961
|
+
# This prevents pytest from discovering tests in parent directories
|
|
962
|
+
rootdir = None
|
|
963
|
+
if hasattr(self, "project_root") and self.project_root:
|
|
964
|
+
rootdir = self.project_root
|
|
965
|
+
elif test_path:
|
|
966
|
+
# Use test_path's parent as rootdir to limit discovery
|
|
967
|
+
rootdir = test_path.resolve().parent
|
|
968
|
+
else:
|
|
969
|
+
# Use current working directory
|
|
970
|
+
rootdir = Path.cwd()
|
|
971
|
+
|
|
972
|
+
# Add rootdir to limit pytest's search scope
|
|
973
|
+
if rootdir and rootdir.exists():
|
|
974
|
+
cmd.extend(["--rootdir", str(rootdir)])
|
|
975
|
+
|
|
976
|
+
# Use parallel execution and unit test marker when running all tests
|
|
977
|
+
# (not when a specific test_path is provided, as it might be integration/e2e)
|
|
978
|
+
if not test_path:
|
|
979
|
+
cmd.extend(["-m", "unit", "-n", "auto"])
|
|
980
|
+
else:
|
|
981
|
+
# Still use parallel execution for specific paths (faster)
|
|
982
|
+
cmd.extend(["-n", "auto"])
|
|
983
|
+
|
|
984
|
+
if coverage and source_paths:
|
|
985
|
+
# Add coverage options
|
|
986
|
+
source_modules = ",".join([str(Path(p).stem) for p in source_paths])
|
|
987
|
+
cmd.extend(
|
|
988
|
+
[
|
|
989
|
+
"--cov",
|
|
990
|
+
source_modules,
|
|
991
|
+
"--cov-report=term-missing",
|
|
992
|
+
"--cov-report=json:coverage.json",
|
|
993
|
+
]
|
|
994
|
+
)
|
|
995
|
+
elif coverage:
|
|
996
|
+
# Coverage for all modules
|
|
997
|
+
cmd.extend(
|
|
998
|
+
[
|
|
999
|
+
"--cov",
|
|
1000
|
+
".",
|
|
1001
|
+
"--cov-report=term-missing",
|
|
1002
|
+
"--cov-report=json:coverage.json",
|
|
1003
|
+
]
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
if test_path:
|
|
1007
|
+
cmd.append(str(test_path))
|
|
1008
|
+
|
|
1009
|
+
try:
|
|
1010
|
+
result = subprocess.run( # nosec B603
|
|
1011
|
+
cmd, capture_output=True, text=True, timeout=300 # 5 minute timeout
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
# Parse coverage if available
|
|
1015
|
+
coverage_data = None
|
|
1016
|
+
if coverage and Path("coverage.json").exists():
|
|
1017
|
+
try:
|
|
1018
|
+
with open("coverage.json", encoding="utf-8") as f:
|
|
1019
|
+
coverage_data = json.load(f)
|
|
1020
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
|
1021
|
+
pass
|
|
1022
|
+
|
|
1023
|
+
# Extract summary from stdout
|
|
1024
|
+
summary_match = re.search(
|
|
1025
|
+
r"(\d+) (passed|failed|error)", result.stdout, re.IGNORECASE
|
|
1026
|
+
)
|
|
1027
|
+
summary = None
|
|
1028
|
+
if summary_match:
|
|
1029
|
+
summary = {
|
|
1030
|
+
"count": int(summary_match.group(1)),
|
|
1031
|
+
"status": summary_match.group(2).lower(),
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return {
|
|
1035
|
+
"success": result.returncode == 0,
|
|
1036
|
+
"return_code": result.returncode,
|
|
1037
|
+
"stdout": result.stdout,
|
|
1038
|
+
"stderr": result.stderr,
|
|
1039
|
+
"summary": summary,
|
|
1040
|
+
"coverage": coverage_data.get("totals") if coverage_data else None,
|
|
1041
|
+
}
|
|
1042
|
+
except subprocess.TimeoutExpired:
|
|
1043
|
+
return {
|
|
1044
|
+
"success": False,
|
|
1045
|
+
"error": "Test execution timeout (5 minutes)",
|
|
1046
|
+
"return_code": -1,
|
|
1047
|
+
}
|
|
1048
|
+
except Exception as e:
|
|
1049
|
+
return {"success": False, "error": str(e), "return_code": -1}
|
|
1050
|
+
|
|
1051
|
+
def _help(self) -> dict[str, Any]:
|
|
1052
|
+
"""
|
|
1053
|
+
Return help information for Tester Agent.
|
|
1054
|
+
|
|
1055
|
+
Returns standardized help format with commands and usage examples.
|
|
1056
|
+
|
|
1057
|
+
Returns:
|
|
1058
|
+
dict: Help information with standardized format:
|
|
1059
|
+
- type (str): Always "help"
|
|
1060
|
+
- content (str): Formatted help text containing:
|
|
1061
|
+
- Available commands (from format_help())
|
|
1062
|
+
- Usage examples for test, generate-tests, and run-tests commands
|
|
1063
|
+
|
|
1064
|
+
Note:
|
|
1065
|
+
This method is synchronous as it performs no I/O operations.
|
|
1066
|
+
Called via agent.run("help") which handles async context.
|
|
1067
|
+
Uses BaseAgent.format_help() which caches command list for performance.
|
|
1068
|
+
"""
|
|
1069
|
+
# Optimize string building by using list and join
|
|
1070
|
+
examples = [
|
|
1071
|
+
" *test file.py # Generate and run tests for file.py",
|
|
1072
|
+
" *generate-tests file.py # Generate tests only",
|
|
1073
|
+
" *run-tests # Run all tests in tests/",
|
|
1074
|
+
" *run-tests test_file.py # Run specific test file",
|
|
1075
|
+
]
|
|
1076
|
+
help_text = "\n".join([self.format_help(), "\nExamples:", *examples])
|
|
1077
|
+
return {"type": "help", "content": help_text}
|
|
1078
|
+
|
|
1079
|
+
async def close(self):
|
|
1080
|
+
"""Close agent and clean up resources."""
|