attune-ai 2.0.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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""Progressive test generation workflow with tier escalation.
|
|
2
|
+
|
|
3
|
+
This module implements test generation with automatic escalation from cheap
|
|
4
|
+
to capable to premium tiers based on test quality metrics.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import logging
|
|
9
|
+
import subprocess
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from attune.workflows.progressive.core import (
|
|
15
|
+
EscalationConfig,
|
|
16
|
+
FailureAnalysis,
|
|
17
|
+
ProgressiveWorkflowResult,
|
|
18
|
+
Tier,
|
|
19
|
+
TierResult,
|
|
20
|
+
)
|
|
21
|
+
from attune.workflows.progressive.workflow import ProgressiveWorkflow
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ProgressiveTestGenWorkflow(ProgressiveWorkflow):
|
|
27
|
+
"""Test generation workflow with progressive tier escalation.
|
|
28
|
+
|
|
29
|
+
Generates tests for Python functions using a cost-efficient progressive
|
|
30
|
+
approach:
|
|
31
|
+
1. Start with cheap tier (gpt-4o-mini) for volume
|
|
32
|
+
2. Escalate failed tests to capable tier (claude-3-5-sonnet)
|
|
33
|
+
3. Escalate persistent failures to premium tier (claude-opus-4)
|
|
34
|
+
|
|
35
|
+
Quality metrics tracked:
|
|
36
|
+
- Syntax errors (AST parsing)
|
|
37
|
+
- Test execution (pass/fail)
|
|
38
|
+
- Code coverage
|
|
39
|
+
- Assertion depth
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> config = EscalationConfig(enabled=True, max_cost=10.00)
|
|
43
|
+
>>> workflow = ProgressiveTestGenWorkflow(config)
|
|
44
|
+
>>> result = workflow.execute(target_file="app.py")
|
|
45
|
+
>>> print(result.generate_report())
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, config: EscalationConfig | None = None):
|
|
49
|
+
"""Initialize progressive test generation workflow.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
config: Escalation configuration (uses defaults if None)
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(config)
|
|
55
|
+
self.target_file: Path | None = None
|
|
56
|
+
|
|
57
|
+
def execute(self, target_file: str, **kwargs) -> ProgressiveWorkflowResult:
|
|
58
|
+
"""Generate tests for target file with progressive escalation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
target_file: Path to Python file to generate tests for
|
|
62
|
+
**kwargs: Additional parameters
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Complete workflow results with progression history
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
FileNotFoundError: If target_file doesn't exist
|
|
69
|
+
BudgetExceededError: If cost exceeds budget
|
|
70
|
+
UserCancelledError: If user declines approval
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> result = workflow.execute(target_file="src/app.py")
|
|
74
|
+
>>> print(f"Generated {len(result.final_result.generated_items)} tests")
|
|
75
|
+
"""
|
|
76
|
+
self.target_file = Path(target_file)
|
|
77
|
+
|
|
78
|
+
if not self.target_file.exists():
|
|
79
|
+
raise FileNotFoundError(f"Target file not found: {target_file}")
|
|
80
|
+
|
|
81
|
+
logger.info(f"Generating tests for {target_file}")
|
|
82
|
+
|
|
83
|
+
# Parse target file to extract functions
|
|
84
|
+
functions = self._parse_functions(self.target_file)
|
|
85
|
+
|
|
86
|
+
if not functions:
|
|
87
|
+
logger.warning(f"No functions found in {target_file}")
|
|
88
|
+
return self._create_empty_result("test-gen")
|
|
89
|
+
|
|
90
|
+
logger.info(f"Found {len(functions)} functions to test")
|
|
91
|
+
|
|
92
|
+
# Execute with progressive escalation
|
|
93
|
+
return self._execute_progressive(items=functions, workflow_name="test-gen", **kwargs)
|
|
94
|
+
|
|
95
|
+
def _parse_functions(self, file_path: Path) -> list[dict[str, Any]]:
|
|
96
|
+
"""Parse Python file to extract function definitions.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
file_path: Path to Python file
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of function metadata dicts with keys:
|
|
103
|
+
- name: Function name
|
|
104
|
+
- lineno: Line number
|
|
105
|
+
- args: List of argument names
|
|
106
|
+
- docstring: Function docstring (if present)
|
|
107
|
+
- code: Full function source code
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> functions = workflow._parse_functions(Path("app.py"))
|
|
111
|
+
>>> print(functions[0]["name"])
|
|
112
|
+
'calculate_total'
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
source = file_path.read_text()
|
|
116
|
+
tree = ast.parse(source)
|
|
117
|
+
except SyntaxError as e:
|
|
118
|
+
logger.error(f"Syntax error in {file_path}: {e}")
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
functions = []
|
|
122
|
+
|
|
123
|
+
for node in ast.walk(tree):
|
|
124
|
+
if isinstance(node, ast.FunctionDef):
|
|
125
|
+
# Extract function info
|
|
126
|
+
func_info = {
|
|
127
|
+
"name": node.name,
|
|
128
|
+
"lineno": node.lineno,
|
|
129
|
+
"args": [arg.arg for arg in node.args.args],
|
|
130
|
+
"docstring": ast.get_docstring(node) or "",
|
|
131
|
+
"code": ast.unparse(node), # Python 3.9+
|
|
132
|
+
"file": str(file_path),
|
|
133
|
+
}
|
|
134
|
+
functions.append(func_info)
|
|
135
|
+
|
|
136
|
+
return functions
|
|
137
|
+
|
|
138
|
+
def _execute_tier_impl(
|
|
139
|
+
self, tier: Tier, items: list[Any], context: dict[str, Any] | None, **kwargs
|
|
140
|
+
) -> list[dict[str, Any]]:
|
|
141
|
+
"""Execute test generation at specific tier.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
tier: Which tier to execute at
|
|
145
|
+
items: Functions to generate tests for
|
|
146
|
+
context: Context from previous tier (if escalating)
|
|
147
|
+
**kwargs: Additional parameters
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
List of generated test items with quality scores
|
|
151
|
+
|
|
152
|
+
Note:
|
|
153
|
+
This is a placeholder implementation. In production, this would
|
|
154
|
+
call the actual LLM API to generate tests.
|
|
155
|
+
"""
|
|
156
|
+
logger.info(f"Generating {len(items)} tests at {tier.value} tier")
|
|
157
|
+
|
|
158
|
+
# Build prompt for this tier (prepared for future LLM integration)
|
|
159
|
+
base_task = self._build_test_gen_task(items)
|
|
160
|
+
_prompt = self.meta_orchestrator.build_tier_prompt(tier, base_task, context) # noqa: F841
|
|
161
|
+
|
|
162
|
+
# TODO: Call LLM API with _prompt
|
|
163
|
+
# For now, simulate test generation
|
|
164
|
+
generated_tests = self._simulate_test_generation(tier, items)
|
|
165
|
+
|
|
166
|
+
return generated_tests
|
|
167
|
+
|
|
168
|
+
def _build_test_gen_task(self, functions: list[dict[str, Any]]) -> str:
|
|
169
|
+
"""Build task description for test generation.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
functions: List of function metadata
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Task description string
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
>>> task = workflow._build_test_gen_task([{"name": "foo", ...}])
|
|
179
|
+
>>> print(task)
|
|
180
|
+
'Generate pytest tests for 1 functions from app.py'
|
|
181
|
+
"""
|
|
182
|
+
file_name = self.target_file.name if self.target_file else "module"
|
|
183
|
+
func_names = [f["name"] for f in functions]
|
|
184
|
+
|
|
185
|
+
task = f"Generate pytest tests for {len(functions)} function(s) from {file_name}"
|
|
186
|
+
|
|
187
|
+
if len(func_names) <= 3:
|
|
188
|
+
task += f": {', '.join(func_names)}"
|
|
189
|
+
|
|
190
|
+
return task
|
|
191
|
+
|
|
192
|
+
def _simulate_test_generation(
|
|
193
|
+
self, tier: Tier, functions: list[dict[str, Any]]
|
|
194
|
+
) -> list[dict[str, Any]]:
|
|
195
|
+
"""Simulate test generation (placeholder for LLM integration).
|
|
196
|
+
|
|
197
|
+
In production, this would call the LLM API. For now, it generates
|
|
198
|
+
mock test data with varying quality based on tier.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
tier: Which tier is generating
|
|
202
|
+
functions: Functions to generate tests for
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
List of generated test items with quality metrics
|
|
206
|
+
|
|
207
|
+
Note:
|
|
208
|
+
This is temporary scaffolding. Real implementation will:
|
|
209
|
+
1. Call LLM API with tier-appropriate model
|
|
210
|
+
2. Parse generated test code
|
|
211
|
+
3. Validate syntax
|
|
212
|
+
4. Execute tests
|
|
213
|
+
5. Calculate coverage
|
|
214
|
+
"""
|
|
215
|
+
generated_tests = []
|
|
216
|
+
|
|
217
|
+
# Quality thresholds per tier (prepared for future LLM integration)
|
|
218
|
+
_base_quality = { # noqa: F841
|
|
219
|
+
Tier.CHEAP: 70,
|
|
220
|
+
Tier.CAPABLE: 85,
|
|
221
|
+
Tier.PREMIUM: 95,
|
|
222
|
+
}[tier]
|
|
223
|
+
|
|
224
|
+
for func in functions:
|
|
225
|
+
# Generate mock test code
|
|
226
|
+
test_code = self._generate_mock_test(func)
|
|
227
|
+
|
|
228
|
+
# Analyze test quality
|
|
229
|
+
analysis = self._analyze_generated_test(test_code, func)
|
|
230
|
+
|
|
231
|
+
# Calculate quality score
|
|
232
|
+
quality_score = analysis.calculate_quality_score()
|
|
233
|
+
|
|
234
|
+
generated_tests.append(
|
|
235
|
+
{
|
|
236
|
+
"function_name": func["name"],
|
|
237
|
+
"test_code": test_code,
|
|
238
|
+
"quality_score": quality_score,
|
|
239
|
+
"passed": analysis.test_pass_rate > 0.5,
|
|
240
|
+
"coverage": analysis.coverage_percent,
|
|
241
|
+
"assertions": analysis.assertion_depth,
|
|
242
|
+
"confidence": analysis.confidence_score,
|
|
243
|
+
"syntax_errors": [str(e) for e in analysis.syntax_errors],
|
|
244
|
+
"error": "" if not analysis.syntax_errors else str(analysis.syntax_errors[0]),
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return generated_tests
|
|
249
|
+
|
|
250
|
+
def _generate_mock_test(self, func: dict[str, Any]) -> str:
|
|
251
|
+
"""Generate mock test code (placeholder).
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
func: Function metadata
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Generated test code as string
|
|
258
|
+
"""
|
|
259
|
+
func_name = func["name"]
|
|
260
|
+
args = func["args"]
|
|
261
|
+
|
|
262
|
+
# Generate simple test template
|
|
263
|
+
test_code = f'''def test_{func_name}():
|
|
264
|
+
"""Test {func_name} function."""
|
|
265
|
+
# Arrange
|
|
266
|
+
{self._generate_test_setup(args)}
|
|
267
|
+
|
|
268
|
+
# Act
|
|
269
|
+
result = {func_name}({", ".join(args)})
|
|
270
|
+
|
|
271
|
+
# Assert
|
|
272
|
+
assert result is not None
|
|
273
|
+
'''
|
|
274
|
+
|
|
275
|
+
return test_code
|
|
276
|
+
|
|
277
|
+
def _generate_test_setup(self, args: list[str]) -> str:
|
|
278
|
+
"""Generate test setup code for arguments.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
args: List of argument names
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Setup code as string
|
|
285
|
+
"""
|
|
286
|
+
if not args:
|
|
287
|
+
return "pass"
|
|
288
|
+
|
|
289
|
+
setup_lines = []
|
|
290
|
+
for arg in args:
|
|
291
|
+
# Simple type inference based on name
|
|
292
|
+
if "count" in arg or "num" in arg or "index" in arg:
|
|
293
|
+
setup_lines.append(f"{arg} = 1")
|
|
294
|
+
elif "name" in arg or "text" in arg or "message" in arg:
|
|
295
|
+
setup_lines.append(f'{arg} = "test"')
|
|
296
|
+
elif "items" in arg or "list" in arg:
|
|
297
|
+
setup_lines.append(f"{arg} = []")
|
|
298
|
+
else:
|
|
299
|
+
setup_lines.append(f'{arg} = "value"')
|
|
300
|
+
|
|
301
|
+
return "\n ".join(setup_lines)
|
|
302
|
+
|
|
303
|
+
def _analyze_generated_test(self, test_code: str, func: dict[str, Any]) -> FailureAnalysis:
|
|
304
|
+
"""Analyze quality of generated test.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
test_code: Generated test code
|
|
308
|
+
func: Original function metadata
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Failure analysis with quality metrics
|
|
312
|
+
"""
|
|
313
|
+
analysis = FailureAnalysis()
|
|
314
|
+
|
|
315
|
+
# 1. Check syntax
|
|
316
|
+
try:
|
|
317
|
+
ast.parse(test_code)
|
|
318
|
+
except SyntaxError as e:
|
|
319
|
+
analysis.syntax_errors.append(e)
|
|
320
|
+
return analysis # Can't proceed with invalid syntax
|
|
321
|
+
|
|
322
|
+
# 2. Count assertions
|
|
323
|
+
try:
|
|
324
|
+
tree = ast.parse(test_code)
|
|
325
|
+
assertion_count = sum(1 for node in ast.walk(tree) if isinstance(node, ast.Assert))
|
|
326
|
+
analysis.assertion_depth = assertion_count
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.warning(f"Failed to count assertions: {e}")
|
|
329
|
+
analysis.assertion_depth = 0
|
|
330
|
+
|
|
331
|
+
# 3. Simulate test execution (placeholder)
|
|
332
|
+
# In production, would actually run the test
|
|
333
|
+
analysis.test_pass_rate = 0.8 # Mock: 80% pass rate
|
|
334
|
+
|
|
335
|
+
# 4. Simulate coverage (placeholder)
|
|
336
|
+
# In production, would use coverage.py
|
|
337
|
+
analysis.coverage_percent = 75.0 # Mock: 75% coverage
|
|
338
|
+
|
|
339
|
+
# 5. Estimate confidence (placeholder)
|
|
340
|
+
# In production, would parse from LLM response
|
|
341
|
+
analysis.confidence_score = 0.85 # Mock: 85% confidence
|
|
342
|
+
|
|
343
|
+
return analysis
|
|
344
|
+
|
|
345
|
+
def _create_empty_result(self, workflow_name: str) -> ProgressiveWorkflowResult:
|
|
346
|
+
"""Create empty result when no functions found.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
workflow_name: Name of workflow
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Empty workflow result
|
|
353
|
+
"""
|
|
354
|
+
empty_result = TierResult(
|
|
355
|
+
tier=Tier.CHEAP,
|
|
356
|
+
model=self._get_model_for_tier(Tier.CHEAP),
|
|
357
|
+
attempt=1,
|
|
358
|
+
timestamp=datetime.now(),
|
|
359
|
+
generated_items=[],
|
|
360
|
+
failure_analysis=FailureAnalysis(),
|
|
361
|
+
cost=0.0,
|
|
362
|
+
duration=0.0,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
task_id = f"{workflow_name}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
366
|
+
|
|
367
|
+
return ProgressiveWorkflowResult(
|
|
368
|
+
workflow_name=workflow_name,
|
|
369
|
+
task_id=task_id,
|
|
370
|
+
tier_results=[empty_result],
|
|
371
|
+
final_result=empty_result,
|
|
372
|
+
total_cost=0.0,
|
|
373
|
+
total_duration=0.0,
|
|
374
|
+
success=False,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def execute_test_file(test_file: Path) -> dict[str, Any]:
|
|
379
|
+
"""Execute a test file using pytest.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
test_file: Path to test file
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Dict with execution results:
|
|
386
|
+
- passed: Number of tests passed
|
|
387
|
+
- failed: Number of tests failed
|
|
388
|
+
- pass_rate: Percentage passed (0.0-1.0)
|
|
389
|
+
- output: pytest output
|
|
390
|
+
|
|
391
|
+
Example:
|
|
392
|
+
>>> result = execute_test_file(Path("test_app.py"))
|
|
393
|
+
>>> print(f"Pass rate: {result['pass_rate']:.1%}")
|
|
394
|
+
"""
|
|
395
|
+
try:
|
|
396
|
+
result = subprocess.run(
|
|
397
|
+
["pytest", str(test_file), "-v", "--tb=short"],
|
|
398
|
+
capture_output=True,
|
|
399
|
+
text=True,
|
|
400
|
+
timeout=60,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# Parse pytest output to get pass/fail counts
|
|
404
|
+
# This is a simple parser - production would be more robust
|
|
405
|
+
output = result.stdout + result.stderr
|
|
406
|
+
|
|
407
|
+
passed = output.count(" PASSED")
|
|
408
|
+
failed = output.count(" FAILED")
|
|
409
|
+
total = passed + failed
|
|
410
|
+
|
|
411
|
+
pass_rate = passed / total if total > 0 else 0.0
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
"passed": passed,
|
|
415
|
+
"failed": failed,
|
|
416
|
+
"total": total,
|
|
417
|
+
"pass_rate": pass_rate,
|
|
418
|
+
"output": output,
|
|
419
|
+
"returncode": result.returncode,
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
except subprocess.TimeoutExpired:
|
|
423
|
+
return {
|
|
424
|
+
"passed": 0,
|
|
425
|
+
"failed": 0,
|
|
426
|
+
"total": 0,
|
|
427
|
+
"pass_rate": 0.0,
|
|
428
|
+
"output": "Test execution timed out",
|
|
429
|
+
"returncode": -1,
|
|
430
|
+
}
|
|
431
|
+
except Exception as e:
|
|
432
|
+
logger.error(f"Failed to execute tests: {e}")
|
|
433
|
+
return {
|
|
434
|
+
"passed": 0,
|
|
435
|
+
"failed": 0,
|
|
436
|
+
"total": 0,
|
|
437
|
+
"pass_rate": 0.0,
|
|
438
|
+
"output": str(e),
|
|
439
|
+
"returncode": -1,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def calculate_coverage(test_file: Path, source_file: Path) -> float:
|
|
444
|
+
"""Calculate code coverage for a test file.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
test_file: Path to test file
|
|
448
|
+
source_file: Path to source file being tested
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Coverage percentage (0.0-100.0)
|
|
452
|
+
|
|
453
|
+
Example:
|
|
454
|
+
>>> coverage = calculate_coverage(
|
|
455
|
+
... Path("test_app.py"),
|
|
456
|
+
... Path("app.py")
|
|
457
|
+
... )
|
|
458
|
+
>>> print(f"Coverage: {coverage:.1f}%")
|
|
459
|
+
"""
|
|
460
|
+
try:
|
|
461
|
+
# Run pytest with coverage
|
|
462
|
+
result = subprocess.run(
|
|
463
|
+
[
|
|
464
|
+
"pytest",
|
|
465
|
+
str(test_file),
|
|
466
|
+
f"--cov={source_file.stem}",
|
|
467
|
+
"--cov-report=term-missing",
|
|
468
|
+
"--no-cov-on-fail",
|
|
469
|
+
],
|
|
470
|
+
capture_output=True,
|
|
471
|
+
text=True,
|
|
472
|
+
timeout=60,
|
|
473
|
+
cwd=source_file.parent,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
output = result.stdout + result.stderr
|
|
477
|
+
|
|
478
|
+
# Parse coverage percentage from output
|
|
479
|
+
# Look for line like: "app.py 85%"
|
|
480
|
+
for line in output.split("\n"):
|
|
481
|
+
if source_file.name in line and "%" in line:
|
|
482
|
+
# Extract percentage
|
|
483
|
+
parts = line.split()
|
|
484
|
+
for part in parts:
|
|
485
|
+
if "%" in part:
|
|
486
|
+
try:
|
|
487
|
+
return float(part.rstrip("%"))
|
|
488
|
+
except ValueError:
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
return 0.0
|
|
492
|
+
|
|
493
|
+
except Exception as e:
|
|
494
|
+
logger.error(f"Failed to calculate coverage: {e}")
|
|
495
|
+
return 0.0
|