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,969 @@
|
|
|
1
|
+
"""Release Preparation Crew - Multi-Agent Workflow
|
|
2
|
+
|
|
3
|
+
.. deprecated:: 4.3.0
|
|
4
|
+
This workflow is deprecated in favor of the meta-workflow system.
|
|
5
|
+
Use ``empathy meta-workflow run release-prep`` instead.
|
|
6
|
+
See docs/CREWAI_MIGRATION.md for migration guide.
|
|
7
|
+
|
|
8
|
+
Comprehensive release readiness assessment using a multi-agent crew.
|
|
9
|
+
|
|
10
|
+
Pattern: Crew
|
|
11
|
+
- Multiple specialized AI agents collaborate on the task
|
|
12
|
+
- Process Type: parallel (agents run simultaneously)
|
|
13
|
+
- Agents: 4
|
|
14
|
+
|
|
15
|
+
Agents:
|
|
16
|
+
- Security Agent: Vulnerability scanning and security audit
|
|
17
|
+
- Testing Agent: Test coverage analysis and quality validation
|
|
18
|
+
- Quality Agent: Code quality review and best practices check
|
|
19
|
+
- Documentation Agent: Documentation completeness verification
|
|
20
|
+
|
|
21
|
+
Copyright 2025 Smart-AI-Memory
|
|
22
|
+
Licensed under Fair Source License 0.9
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
import os
|
|
27
|
+
import warnings
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
# Try to import the LLM executor for actual AI calls
|
|
33
|
+
EmpathyLLMExecutor = None
|
|
34
|
+
ExecutionContext = None
|
|
35
|
+
HAS_EXECUTOR = False
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from attune.models import ExecutionContext as _ExecutionContext
|
|
39
|
+
from attune.models.empathy_executor import EmpathyLLMExecutor as _EmpathyLLMExecutor
|
|
40
|
+
|
|
41
|
+
EmpathyLLMExecutor = _EmpathyLLMExecutor
|
|
42
|
+
ExecutionContext = _ExecutionContext
|
|
43
|
+
HAS_EXECUTOR = True
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
# Try to import the ProjectIndex for file tracking
|
|
48
|
+
ProjectIndex = None
|
|
49
|
+
HAS_PROJECT_INDEX = False
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
from attune.project_index import ProjectIndex as _ProjectIndex
|
|
53
|
+
|
|
54
|
+
ProjectIndex = _ProjectIndex
|
|
55
|
+
HAS_PROJECT_INDEX = True
|
|
56
|
+
except ImportError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class QualityGate:
|
|
62
|
+
"""Quality gate threshold for release readiness."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
threshold: float
|
|
66
|
+
actual: float = 0.0
|
|
67
|
+
passed: bool = False
|
|
68
|
+
critical: bool = True
|
|
69
|
+
message: str = ""
|
|
70
|
+
|
|
71
|
+
def __post_init__(self):
|
|
72
|
+
"""Generate message if not provided."""
|
|
73
|
+
if not self.message:
|
|
74
|
+
status = "✅ PASS" if self.passed else "❌ FAIL"
|
|
75
|
+
self.message = (
|
|
76
|
+
f"{self.name}: {status} "
|
|
77
|
+
f"(actual: {self.actual:.1f}, threshold: {self.threshold:.1f})"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ReleasePreparationCrewResult:
|
|
83
|
+
"""Result from ReleasePreparationCrew execution."""
|
|
84
|
+
|
|
85
|
+
success: bool
|
|
86
|
+
approved: bool # Overall release approval
|
|
87
|
+
confidence: str # "high", "medium", "low"
|
|
88
|
+
|
|
89
|
+
# Quality gates
|
|
90
|
+
quality_gates: list[QualityGate] = field(default_factory=list)
|
|
91
|
+
|
|
92
|
+
# Agent findings
|
|
93
|
+
security_findings: dict = field(default_factory=dict)
|
|
94
|
+
testing_findings: dict = field(default_factory=dict)
|
|
95
|
+
quality_findings: dict = field(default_factory=dict)
|
|
96
|
+
documentation_findings: dict = field(default_factory=dict)
|
|
97
|
+
|
|
98
|
+
# Aggregate metrics
|
|
99
|
+
blockers: list[str] = field(default_factory=list)
|
|
100
|
+
warnings: list[str] = field(default_factory=list)
|
|
101
|
+
recommendations: list[str] = field(default_factory=list)
|
|
102
|
+
|
|
103
|
+
# Cost tracking
|
|
104
|
+
cost: float = 0.0
|
|
105
|
+
duration_ms: int = 0
|
|
106
|
+
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
107
|
+
|
|
108
|
+
def to_dict(self) -> dict:
|
|
109
|
+
"""Convert result to dictionary."""
|
|
110
|
+
return {
|
|
111
|
+
"success": self.success,
|
|
112
|
+
"approved": self.approved,
|
|
113
|
+
"confidence": self.confidence,
|
|
114
|
+
"quality_gates": [
|
|
115
|
+
{
|
|
116
|
+
"name": gate.name,
|
|
117
|
+
"threshold": gate.threshold,
|
|
118
|
+
"actual": gate.actual,
|
|
119
|
+
"passed": gate.passed,
|
|
120
|
+
"critical": gate.critical,
|
|
121
|
+
"message": gate.message,
|
|
122
|
+
}
|
|
123
|
+
for gate in self.quality_gates
|
|
124
|
+
],
|
|
125
|
+
"security_findings": self.security_findings,
|
|
126
|
+
"testing_findings": self.testing_findings,
|
|
127
|
+
"quality_findings": self.quality_findings,
|
|
128
|
+
"documentation_findings": self.documentation_findings,
|
|
129
|
+
"blockers": self.blockers,
|
|
130
|
+
"warnings": self.warnings,
|
|
131
|
+
"recommendations": self.recommendations,
|
|
132
|
+
"cost": self.cost,
|
|
133
|
+
"duration_ms": self.duration_ms,
|
|
134
|
+
"timestamp": self.timestamp,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def formatted_report(self) -> str:
|
|
139
|
+
"""Generate human-readable formatted report."""
|
|
140
|
+
return format_release_prep_report(self)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class Agent:
|
|
145
|
+
"""Agent configuration for the crew with XML-enhanced prompting."""
|
|
146
|
+
|
|
147
|
+
role: str
|
|
148
|
+
goal: str
|
|
149
|
+
backstory: str
|
|
150
|
+
expertise_level: str = "expert"
|
|
151
|
+
use_xml_structure: bool = True
|
|
152
|
+
|
|
153
|
+
def get_system_prompt(self) -> str:
|
|
154
|
+
"""Generate XML-enhanced system prompt for this agent."""
|
|
155
|
+
return f"""<agent_role>
|
|
156
|
+
You are a {self.role} with {self.expertise_level}-level expertise.
|
|
157
|
+
</agent_role>
|
|
158
|
+
|
|
159
|
+
<agent_goal>
|
|
160
|
+
{self.goal}
|
|
161
|
+
</agent_goal>
|
|
162
|
+
|
|
163
|
+
<agent_backstory>
|
|
164
|
+
{self.backstory}
|
|
165
|
+
</agent_backstory>
|
|
166
|
+
|
|
167
|
+
<instructions>
|
|
168
|
+
1. Carefully review all provided context data
|
|
169
|
+
2. Think through your analysis step-by-step
|
|
170
|
+
3. Provide thorough, actionable analysis
|
|
171
|
+
4. Be specific and cite file paths when relevant
|
|
172
|
+
5. Structure your output according to the requested format
|
|
173
|
+
</instructions>
|
|
174
|
+
|
|
175
|
+
<output_structure>
|
|
176
|
+
Always structure your response as:
|
|
177
|
+
|
|
178
|
+
<thinking>
|
|
179
|
+
[Your step-by-step reasoning process]
|
|
180
|
+
- What you observe in the context
|
|
181
|
+
- How you analyze the situation
|
|
182
|
+
- What conclusions you draw
|
|
183
|
+
</thinking>
|
|
184
|
+
|
|
185
|
+
<answer>
|
|
186
|
+
[Your final output in the requested format]
|
|
187
|
+
</answer>
|
|
188
|
+
</output_structure>"""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class Task:
|
|
193
|
+
"""Task configuration for the crew with XML-enhanced prompting."""
|
|
194
|
+
|
|
195
|
+
description: str
|
|
196
|
+
expected_output: str
|
|
197
|
+
agent: Agent
|
|
198
|
+
|
|
199
|
+
def get_user_prompt(self, context: dict) -> str:
|
|
200
|
+
"""Generate XML-enhanced user prompt for this task with context."""
|
|
201
|
+
# Build structured context with proper XML tags
|
|
202
|
+
context_sections = []
|
|
203
|
+
for key, value in context.items():
|
|
204
|
+
if value:
|
|
205
|
+
# Use underscores for tag names
|
|
206
|
+
tag_name = key.replace(" ", "_").replace("-", "_").lower()
|
|
207
|
+
# Wrap in appropriate tags
|
|
208
|
+
context_sections.append(f"<{tag_name}>\n{value}\n</{tag_name}>")
|
|
209
|
+
|
|
210
|
+
context_xml = "\n".join(context_sections)
|
|
211
|
+
|
|
212
|
+
return f"""<task_description>
|
|
213
|
+
{self.description}
|
|
214
|
+
</task_description>
|
|
215
|
+
|
|
216
|
+
<context>
|
|
217
|
+
{context_xml}
|
|
218
|
+
</context>
|
|
219
|
+
|
|
220
|
+
<expected_output>
|
|
221
|
+
{self.expected_output}
|
|
222
|
+
</expected_output>
|
|
223
|
+
|
|
224
|
+
<instructions>
|
|
225
|
+
1. Review all context data in the <context> tags above
|
|
226
|
+
2. Structure your response using <thinking> and <answer> tags as defined in your system prompt
|
|
227
|
+
3. Match the expected output format exactly
|
|
228
|
+
4. Be thorough and specific in your analysis
|
|
229
|
+
</instructions>"""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def parse_xml_response(response: str) -> dict:
|
|
233
|
+
"""Parse XML-structured agent response.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
response: Raw agent response potentially containing XML tags
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Dict with 'thinking' and 'answer' sections (if found) or raw response
|
|
240
|
+
"""
|
|
241
|
+
result = {
|
|
242
|
+
"thinking": "",
|
|
243
|
+
"answer": "",
|
|
244
|
+
"raw": response,
|
|
245
|
+
"has_xml_structure": False,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Try to extract thinking section
|
|
249
|
+
thinking_start = response.find("<thinking>")
|
|
250
|
+
thinking_end = response.find("</thinking>")
|
|
251
|
+
if thinking_start != -1 and thinking_end != -1:
|
|
252
|
+
result["thinking"] = response[thinking_start + 10 : thinking_end].strip()
|
|
253
|
+
result["has_xml_structure"] = True
|
|
254
|
+
|
|
255
|
+
# Try to extract answer section
|
|
256
|
+
answer_start = response.find("<answer>")
|
|
257
|
+
answer_end = response.find("</answer>")
|
|
258
|
+
if answer_start != -1 and answer_end != -1:
|
|
259
|
+
result["answer"] = response[answer_start + 8 : answer_end].strip()
|
|
260
|
+
result["has_xml_structure"] = True
|
|
261
|
+
|
|
262
|
+
# If no XML structure found, use full response as answer
|
|
263
|
+
if not result["has_xml_structure"]:
|
|
264
|
+
result["answer"] = response
|
|
265
|
+
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def format_release_prep_report(result: ReleasePreparationCrewResult) -> str:
|
|
270
|
+
"""Format release preparation result as human-readable text."""
|
|
271
|
+
lines = []
|
|
272
|
+
|
|
273
|
+
# Header
|
|
274
|
+
lines.append("=" * 70)
|
|
275
|
+
lines.append("RELEASE READINESS REPORT (CrewAI Multi-Agent)")
|
|
276
|
+
lines.append("=" * 70)
|
|
277
|
+
lines.append("")
|
|
278
|
+
|
|
279
|
+
# Status
|
|
280
|
+
status_icon = "✅" if result.approved else "❌"
|
|
281
|
+
status_text = "APPROVED FOR RELEASE" if result.approved else "NOT READY FOR RELEASE"
|
|
282
|
+
lines.append(f"Status: {status_icon} {status_text}")
|
|
283
|
+
lines.append(f"Confidence: {result.confidence.upper()}")
|
|
284
|
+
lines.append(f"Generated: {result.timestamp}")
|
|
285
|
+
lines.append(f"Duration: {result.duration_ms}ms ({result.duration_ms / 1000:.1f}s)")
|
|
286
|
+
lines.append(f"Cost: ${result.cost:.4f}")
|
|
287
|
+
lines.append("")
|
|
288
|
+
|
|
289
|
+
# Quality Gates
|
|
290
|
+
if result.quality_gates:
|
|
291
|
+
lines.append("-" * 70)
|
|
292
|
+
lines.append("QUALITY GATES")
|
|
293
|
+
lines.append("-" * 70)
|
|
294
|
+
for gate in result.quality_gates:
|
|
295
|
+
icon = "✅" if gate.passed else ("🔴" if gate.critical else "⚠️")
|
|
296
|
+
lines.append(f"{icon} {gate.message}")
|
|
297
|
+
lines.append("")
|
|
298
|
+
|
|
299
|
+
# Blockers
|
|
300
|
+
if result.blockers:
|
|
301
|
+
lines.append("-" * 70)
|
|
302
|
+
lines.append("🚫 RELEASE BLOCKERS")
|
|
303
|
+
lines.append("-" * 70)
|
|
304
|
+
for blocker in result.blockers:
|
|
305
|
+
lines.append(f" • {blocker}")
|
|
306
|
+
lines.append("")
|
|
307
|
+
|
|
308
|
+
# Warnings
|
|
309
|
+
if result.warnings:
|
|
310
|
+
lines.append("-" * 70)
|
|
311
|
+
lines.append("⚠️ WARNINGS")
|
|
312
|
+
lines.append("-" * 70)
|
|
313
|
+
for warning in result.warnings:
|
|
314
|
+
lines.append(f" • {warning}")
|
|
315
|
+
lines.append("")
|
|
316
|
+
|
|
317
|
+
# Recommendations
|
|
318
|
+
if result.recommendations:
|
|
319
|
+
lines.append("-" * 70)
|
|
320
|
+
lines.append("💡 RECOMMENDATIONS")
|
|
321
|
+
lines.append("-" * 70)
|
|
322
|
+
for i, rec in enumerate(result.recommendations, 1):
|
|
323
|
+
lines.append(f"{i}. {rec}")
|
|
324
|
+
lines.append("")
|
|
325
|
+
|
|
326
|
+
# Agent Findings Summary
|
|
327
|
+
lines.append("-" * 70)
|
|
328
|
+
lines.append("AGENT FINDINGS")
|
|
329
|
+
lines.append("-" * 70)
|
|
330
|
+
|
|
331
|
+
if result.security_findings:
|
|
332
|
+
lines.append("\n🔒 Security Agent:")
|
|
333
|
+
lines.append(f" {result.security_findings.get('summary', 'No summary available')}")
|
|
334
|
+
|
|
335
|
+
if result.testing_findings:
|
|
336
|
+
lines.append("\n🧪 Testing Agent:")
|
|
337
|
+
lines.append(f" {result.testing_findings.get('summary', 'No summary available')}")
|
|
338
|
+
|
|
339
|
+
if result.quality_findings:
|
|
340
|
+
lines.append("\n⚡ Quality Agent:")
|
|
341
|
+
lines.append(f" {result.quality_findings.get('summary', 'No summary available')}")
|
|
342
|
+
|
|
343
|
+
if result.documentation_findings:
|
|
344
|
+
lines.append("\n📝 Documentation Agent:")
|
|
345
|
+
lines.append(f" {result.documentation_findings.get('summary', 'No summary available')}")
|
|
346
|
+
|
|
347
|
+
lines.append("")
|
|
348
|
+
|
|
349
|
+
# Footer
|
|
350
|
+
lines.append("=" * 70)
|
|
351
|
+
if result.approved:
|
|
352
|
+
lines.append("✅ Release preparation complete - ready to ship")
|
|
353
|
+
else:
|
|
354
|
+
lines.append("❌ Release blocked - address issues above before shipping")
|
|
355
|
+
lines.append("=" * 70)
|
|
356
|
+
|
|
357
|
+
return "\n".join(lines)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class ReleasePreparationCrew:
|
|
361
|
+
"""Release Preparation Crew - Multi-agent release readiness assessment.
|
|
362
|
+
|
|
363
|
+
Uses 4 specialized agents running in parallel to comprehensively
|
|
364
|
+
evaluate release readiness across security, testing, quality, and documentation.
|
|
365
|
+
|
|
366
|
+
Process Type: parallel
|
|
367
|
+
|
|
368
|
+
Agents:
|
|
369
|
+
- Security Agent: Vulnerability scanning and security audit
|
|
370
|
+
- Testing Agent: Test coverage analysis and quality validation
|
|
371
|
+
- Quality Agent: Code quality review and best practices check
|
|
372
|
+
- Documentation Agent: Documentation completeness verification
|
|
373
|
+
|
|
374
|
+
Usage:
|
|
375
|
+
crew = ReleasePreparationCrew()
|
|
376
|
+
result = await crew.execute(path="./src")
|
|
377
|
+
|
|
378
|
+
if result.approved:
|
|
379
|
+
print("✅ Ready for release!")
|
|
380
|
+
else:
|
|
381
|
+
for blocker in result.blockers:
|
|
382
|
+
print(f"BLOCKER: {blocker}")
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
name = "Release_Preparation_Crew"
|
|
386
|
+
description = "Comprehensive release readiness assessment using multi-agent crew"
|
|
387
|
+
process_type = "parallel"
|
|
388
|
+
|
|
389
|
+
def __init__(
|
|
390
|
+
self, project_root: str = ".", quality_gates: dict[str, float] | None = None, **kwargs: Any
|
|
391
|
+
):
|
|
392
|
+
"""Initialize the crew with configured agents.
|
|
393
|
+
|
|
394
|
+
.. deprecated:: 4.3.0
|
|
395
|
+
Use meta-workflow system instead: ``empathy meta-workflow run release-prep``
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
project_root: Root directory of project to analyze
|
|
399
|
+
quality_gates: Optional quality gate thresholds
|
|
400
|
+
- security: 0 critical issues (default)
|
|
401
|
+
- coverage: 80% test coverage (default)
|
|
402
|
+
- quality: 7.0 quality score (default)
|
|
403
|
+
- documentation: 100% doc coverage (default)
|
|
404
|
+
**kwargs: Additional configuration
|
|
405
|
+
"""
|
|
406
|
+
warnings.warn(
|
|
407
|
+
"ReleasePreparationCrew is deprecated since v4.3.0. "
|
|
408
|
+
"Use meta-workflow system instead: empathy meta-workflow run release-prep. "
|
|
409
|
+
"See docs/CREWAI_MIGRATION.md for migration guide.",
|
|
410
|
+
DeprecationWarning,
|
|
411
|
+
stacklevel=2,
|
|
412
|
+
)
|
|
413
|
+
self.config = kwargs
|
|
414
|
+
self.project_root = project_root
|
|
415
|
+
self._executor = None
|
|
416
|
+
self._project_index = None
|
|
417
|
+
self._total_cost = 0.0
|
|
418
|
+
self._total_input_tokens = 0
|
|
419
|
+
self._total_output_tokens = 0
|
|
420
|
+
|
|
421
|
+
# Set default quality gates
|
|
422
|
+
self.quality_gates = {
|
|
423
|
+
"security": 0.0, # No critical issues
|
|
424
|
+
"coverage": 80.0, # 80% test coverage
|
|
425
|
+
"quality": 7.0, # Quality score ≥ 7
|
|
426
|
+
"documentation": 100.0, # 100% doc coverage
|
|
427
|
+
}
|
|
428
|
+
if quality_gates:
|
|
429
|
+
self.quality_gates.update(quality_gates)
|
|
430
|
+
|
|
431
|
+
# Initialize executor if available
|
|
432
|
+
if HAS_EXECUTOR and EmpathyLLMExecutor is not None:
|
|
433
|
+
api_key = os.getenv("ANTHROPIC_API_KEY")
|
|
434
|
+
if api_key:
|
|
435
|
+
try:
|
|
436
|
+
self._executor = EmpathyLLMExecutor(
|
|
437
|
+
provider="anthropic",
|
|
438
|
+
api_key=api_key,
|
|
439
|
+
)
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
# Initialize ProjectIndex if available
|
|
444
|
+
if HAS_PROJECT_INDEX and ProjectIndex is not None:
|
|
445
|
+
try:
|
|
446
|
+
self._project_index = ProjectIndex(project_root)
|
|
447
|
+
if not self._project_index.load():
|
|
448
|
+
# Index doesn't exist or is stale, refresh it
|
|
449
|
+
print(" [ProjectIndex] Building index (first run)...")
|
|
450
|
+
self._project_index.refresh()
|
|
451
|
+
except Exception as e:
|
|
452
|
+
print(f" [ProjectIndex] Warning: Could not load index: {e}")
|
|
453
|
+
|
|
454
|
+
# Define agents
|
|
455
|
+
self.security_agent = Agent(
|
|
456
|
+
role="Security Auditor",
|
|
457
|
+
goal="Perform comprehensive security audit and vulnerability scan",
|
|
458
|
+
backstory="Expert security auditor specializing in OWASP Top 10 vulnerabilities, dependency security, and security best practices. Skilled at identifying critical security issues that would block release.",
|
|
459
|
+
expertise_level="expert",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
self.testing_agent = Agent(
|
|
463
|
+
role="Test Coverage Analyst",
|
|
464
|
+
goal="Analyze test coverage and validate testing quality",
|
|
465
|
+
backstory="Testing expert focused on test coverage metrics, test quality, and identifying critical gaps. Ensures adequate testing before release.",
|
|
466
|
+
expertise_level="expert",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
self.quality_agent = Agent(
|
|
470
|
+
role="Code Quality Reviewer",
|
|
471
|
+
goal="Review code quality and adherence to best practices",
|
|
472
|
+
backstory="Senior code reviewer focused on code maintainability, complexity, and best practices. Identifies code quality issues that impact long-term project health.",
|
|
473
|
+
expertise_level="expert",
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
self.documentation_agent = Agent(
|
|
477
|
+
role="Documentation Specialist",
|
|
478
|
+
goal="Verify documentation completeness and accuracy",
|
|
479
|
+
backstory="Technical writer and documentation expert. Ensures all code is properly documented, README is up-to-date, and API docs are complete.",
|
|
480
|
+
expertise_level="expert",
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Store all agents
|
|
484
|
+
self.agents = [
|
|
485
|
+
self.security_agent,
|
|
486
|
+
self.testing_agent,
|
|
487
|
+
self.quality_agent,
|
|
488
|
+
self.documentation_agent,
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
def define_tasks(self) -> list[Task]:
|
|
492
|
+
"""Define the tasks for this crew."""
|
|
493
|
+
return [
|
|
494
|
+
Task(
|
|
495
|
+
description=f"Perform security audit: 1) Scan for OWASP Top 10 vulnerabilities, 2) Check dependency security, 3) Review authentication/authorization, 4) Identify critical security issues. Quality gate: ≤{self.quality_gates['security']} critical issues",
|
|
496
|
+
expected_output="JSON with: critical_issues_count, high_issues_count, findings (list of issues with severity/details), recommendation (GO/NO_GO)",
|
|
497
|
+
agent=self.security_agent,
|
|
498
|
+
),
|
|
499
|
+
Task(
|
|
500
|
+
description=f"Analyze test coverage: 1) Calculate current test coverage percentage, 2) Identify critical gaps in coverage, 3) Assess test quality, 4) Verify tests pass. Quality gate: ≥{self.quality_gates['coverage']}% coverage",
|
|
501
|
+
expected_output="JSON with: coverage_percentage, critical_gaps_count, tests_passing (true/false), recommendation (GO/NO_GO)",
|
|
502
|
+
agent=self.testing_agent,
|
|
503
|
+
),
|
|
504
|
+
Task(
|
|
505
|
+
description=f"Review code quality: 1) Calculate code quality score (0-10), 2) Check for code smells and anti-patterns, 3) Verify linting passes, 4) Assess maintainability. Quality gate: ≥{self.quality_gates['quality']} quality score",
|
|
506
|
+
expected_output="JSON with: quality_score, complexity_issues, linting_errors, recommendation (GO/NO_GO)",
|
|
507
|
+
agent=self.quality_agent,
|
|
508
|
+
),
|
|
509
|
+
Task(
|
|
510
|
+
description=f"Verify documentation: 1) Check docstring coverage, 2) Verify README is current, 3) Validate API documentation, 4) Check for missing docs. Quality gate: ≥{self.quality_gates['documentation']}% doc coverage",
|
|
511
|
+
expected_output="JSON with: docstring_coverage_percentage, readme_current (true/false), missing_docs_count, recommendation (GO/NO_GO)",
|
|
512
|
+
agent=self.documentation_agent,
|
|
513
|
+
),
|
|
514
|
+
]
|
|
515
|
+
|
|
516
|
+
async def _call_llm(
|
|
517
|
+
self,
|
|
518
|
+
agent: Agent,
|
|
519
|
+
task: Task,
|
|
520
|
+
context: dict,
|
|
521
|
+
task_type: str = "release_validation",
|
|
522
|
+
) -> tuple[str, int, int, float]:
|
|
523
|
+
"""Call the LLM with agent/task configuration.
|
|
524
|
+
|
|
525
|
+
Returns: (response_text, input_tokens, output_tokens, cost)
|
|
526
|
+
"""
|
|
527
|
+
system_prompt = agent.get_system_prompt()
|
|
528
|
+
user_prompt = task.get_user_prompt(context)
|
|
529
|
+
|
|
530
|
+
if self._executor is None:
|
|
531
|
+
# Fallback: return mock response
|
|
532
|
+
return await self._mock_llm_call(agent, task)
|
|
533
|
+
|
|
534
|
+
try:
|
|
535
|
+
# Create execution context
|
|
536
|
+
exec_context = ExecutionContext(
|
|
537
|
+
task_type=task_type,
|
|
538
|
+
workflow_name="release-prep",
|
|
539
|
+
step_name=agent.role,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# Execute with timeout using correct LLMExecutor API
|
|
543
|
+
result = await asyncio.wait_for(
|
|
544
|
+
self._executor.run(
|
|
545
|
+
task_type=task_type,
|
|
546
|
+
prompt=user_prompt,
|
|
547
|
+
system=system_prompt,
|
|
548
|
+
context=exec_context,
|
|
549
|
+
),
|
|
550
|
+
timeout=120.0, # 2 minute timeout
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
response = result.content
|
|
554
|
+
input_tokens = result.input_tokens
|
|
555
|
+
output_tokens = result.output_tokens
|
|
556
|
+
cost = result.cost
|
|
557
|
+
|
|
558
|
+
# Track totals
|
|
559
|
+
self._total_cost += cost
|
|
560
|
+
self._total_input_tokens += input_tokens
|
|
561
|
+
self._total_output_tokens += output_tokens
|
|
562
|
+
|
|
563
|
+
return (response, input_tokens, output_tokens, cost)
|
|
564
|
+
|
|
565
|
+
except asyncio.TimeoutError:
|
|
566
|
+
print(f" [LLM] Timeout calling {agent.role}")
|
|
567
|
+
return await self._mock_llm_call(agent, task, reason="Timeout")
|
|
568
|
+
except Exception as e:
|
|
569
|
+
print(f" [LLM] Error calling {agent.role}: {e}")
|
|
570
|
+
return await self._mock_llm_call(agent, task, reason=str(e))
|
|
571
|
+
|
|
572
|
+
async def _mock_llm_call(
|
|
573
|
+
self, agent: Agent, task: Task, reason: str = "No API key"
|
|
574
|
+
) -> tuple[str, int, int, float]:
|
|
575
|
+
"""Generate mock response when LLM is unavailable."""
|
|
576
|
+
# Simulate brief delay
|
|
577
|
+
await asyncio.sleep(0.1)
|
|
578
|
+
|
|
579
|
+
mock_findings = {
|
|
580
|
+
"Security Auditor": f"""[Mock Security Audit - {reason}]
|
|
581
|
+
|
|
582
|
+
<thinking>
|
|
583
|
+
Performing security audit of the codebase...
|
|
584
|
+
- Scanning for OWASP Top 10 vulnerabilities
|
|
585
|
+
- Checking dependency versions
|
|
586
|
+
- Reviewing authentication mechanisms
|
|
587
|
+
</thinking>
|
|
588
|
+
|
|
589
|
+
<answer>
|
|
590
|
+
{{
|
|
591
|
+
"critical_issues_count": 0,
|
|
592
|
+
"high_issues_count": 2,
|
|
593
|
+
"findings": [
|
|
594
|
+
{{"severity": "high", "details": "Outdated dependency: requests 2.25.0 (CVE-2024-XXXX)"}},
|
|
595
|
+
{{"severity": "medium", "details": "Missing rate limiting on API endpoints"}}
|
|
596
|
+
],
|
|
597
|
+
"recommendation": "GO (no critical blockers, address high issues post-release)"
|
|
598
|
+
}}
|
|
599
|
+
</answer>
|
|
600
|
+
|
|
601
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
602
|
+
"Test Coverage Analyst": f"""[Mock Coverage Analysis - {reason}]
|
|
603
|
+
|
|
604
|
+
<thinking>
|
|
605
|
+
Analyzing test coverage across the codebase...
|
|
606
|
+
- Running coverage tools
|
|
607
|
+
- Identifying critical gaps
|
|
608
|
+
- Verifying tests pass
|
|
609
|
+
</thinking>
|
|
610
|
+
|
|
611
|
+
<answer>
|
|
612
|
+
{{
|
|
613
|
+
"coverage_percentage": 75.5,
|
|
614
|
+
"critical_gaps_count": 3,
|
|
615
|
+
"tests_passing": true,
|
|
616
|
+
"recommendation": "NO_GO (coverage below 80% threshold)"
|
|
617
|
+
}}
|
|
618
|
+
</answer>
|
|
619
|
+
|
|
620
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
621
|
+
"Code Quality Reviewer": f"""[Mock Quality Review - {reason}]
|
|
622
|
+
|
|
623
|
+
<thinking>
|
|
624
|
+
Reviewing code quality metrics...
|
|
625
|
+
- Calculating complexity scores
|
|
626
|
+
- Checking linting status
|
|
627
|
+
- Assessing maintainability
|
|
628
|
+
</thinking>
|
|
629
|
+
|
|
630
|
+
<answer>
|
|
631
|
+
{{
|
|
632
|
+
"quality_score": 8.2,
|
|
633
|
+
"complexity_issues": 2,
|
|
634
|
+
"linting_errors": 0,
|
|
635
|
+
"recommendation": "GO (quality score above threshold)"
|
|
636
|
+
}}
|
|
637
|
+
</answer>
|
|
638
|
+
|
|
639
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
640
|
+
"Documentation Specialist": f"""[Mock Documentation Check - {reason}]
|
|
641
|
+
|
|
642
|
+
<thinking>
|
|
643
|
+
Verifying documentation completeness...
|
|
644
|
+
- Checking docstring coverage
|
|
645
|
+
- Reviewing README currency
|
|
646
|
+
- Validating API docs
|
|
647
|
+
</thinking>
|
|
648
|
+
|
|
649
|
+
<answer>
|
|
650
|
+
{{
|
|
651
|
+
"docstring_coverage_percentage": 92.0,
|
|
652
|
+
"readme_current": false,
|
|
653
|
+
"missing_docs_count": 5,
|
|
654
|
+
"recommendation": "CONDITIONAL (README needs update)"
|
|
655
|
+
}}
|
|
656
|
+
</answer>
|
|
657
|
+
|
|
658
|
+
Note: This is a mock response. Configure ANTHROPIC_API_KEY for real analysis.""",
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
response = mock_findings.get(agent.role, f"Mock response for {agent.role}")
|
|
662
|
+
return (response, 0, 0, 0.0)
|
|
663
|
+
|
|
664
|
+
def _get_index_context(self) -> dict[str, Any]:
|
|
665
|
+
"""Get release validation context from ProjectIndex if available."""
|
|
666
|
+
if self._project_index is None:
|
|
667
|
+
return {}
|
|
668
|
+
|
|
669
|
+
try:
|
|
670
|
+
return self._project_index.get_context_for_workflow("release_prep")
|
|
671
|
+
except Exception as e:
|
|
672
|
+
print(f" [ProjectIndex] Warning: Could not get context: {e}")
|
|
673
|
+
return {}
|
|
674
|
+
|
|
675
|
+
async def execute(
|
|
676
|
+
self,
|
|
677
|
+
path: str = ".",
|
|
678
|
+
context: dict | None = None,
|
|
679
|
+
**kwargs: Any,
|
|
680
|
+
) -> ReleasePreparationCrewResult:
|
|
681
|
+
"""Execute the release preparation crew.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
path: Path to analyze for release readiness
|
|
685
|
+
context: Additional context for agents
|
|
686
|
+
**kwargs: Additional arguments
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
ReleasePreparationCrewResult with approval status and findings
|
|
690
|
+
"""
|
|
691
|
+
started_at = datetime.now()
|
|
692
|
+
context = context or {}
|
|
693
|
+
|
|
694
|
+
print("\n" + "=" * 70)
|
|
695
|
+
print(" RELEASE PREPARATION CREW (CrewAI)")
|
|
696
|
+
print("=" * 70)
|
|
697
|
+
print(f"\n Project Path: {path}")
|
|
698
|
+
print(f" Agents: {len(self.agents)} (running in parallel)")
|
|
699
|
+
print("")
|
|
700
|
+
|
|
701
|
+
# Try to get rich context from ProjectIndex
|
|
702
|
+
index_context = self._get_index_context()
|
|
703
|
+
|
|
704
|
+
if index_context:
|
|
705
|
+
print(" [ProjectIndex] Using indexed project data")
|
|
706
|
+
agent_context = {
|
|
707
|
+
"path": path,
|
|
708
|
+
**index_context,
|
|
709
|
+
**context,
|
|
710
|
+
}
|
|
711
|
+
else:
|
|
712
|
+
# Fallback: basic context
|
|
713
|
+
agent_context = {
|
|
714
|
+
"path": path,
|
|
715
|
+
"quality_gates": self.quality_gates,
|
|
716
|
+
**context,
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
# Define tasks
|
|
720
|
+
tasks = self.define_tasks()
|
|
721
|
+
|
|
722
|
+
# Execute all agents in parallel
|
|
723
|
+
print(" 🚀 Executing agents in parallel...\n")
|
|
724
|
+
|
|
725
|
+
agent_tasks = []
|
|
726
|
+
for agent, task in zip(self.agents, tasks, strict=False):
|
|
727
|
+
print(f" • {agent.role}")
|
|
728
|
+
agent_tasks.append(self._call_llm(agent, task, agent_context))
|
|
729
|
+
|
|
730
|
+
# Wait for all agents to complete
|
|
731
|
+
results = await asyncio.gather(*agent_tasks)
|
|
732
|
+
|
|
733
|
+
print("\n ✓ All agents completed\n")
|
|
734
|
+
|
|
735
|
+
# Parse agent responses
|
|
736
|
+
agent_findings = []
|
|
737
|
+
for agent, _, (response, input_tokens, output_tokens, cost) in zip(
|
|
738
|
+
self.agents, tasks, results, strict=False
|
|
739
|
+
):
|
|
740
|
+
parsed = parse_xml_response(response)
|
|
741
|
+
agent_findings.append(
|
|
742
|
+
{
|
|
743
|
+
"agent": agent.role,
|
|
744
|
+
"response": response,
|
|
745
|
+
"thinking": parsed["thinking"],
|
|
746
|
+
"answer": parsed["answer"],
|
|
747
|
+
"has_xml_structure": parsed["has_xml_structure"],
|
|
748
|
+
"cost": cost,
|
|
749
|
+
"input_tokens": input_tokens,
|
|
750
|
+
"output_tokens": output_tokens,
|
|
751
|
+
}
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Extract structured findings from each agent
|
|
755
|
+
security_findings = self._extract_security_findings(agent_findings[0])
|
|
756
|
+
testing_findings = self._extract_testing_findings(agent_findings[1])
|
|
757
|
+
quality_findings = self._extract_quality_findings(agent_findings[2])
|
|
758
|
+
documentation_findings = self._extract_documentation_findings(agent_findings[3])
|
|
759
|
+
|
|
760
|
+
# Evaluate quality gates
|
|
761
|
+
quality_gates = self._evaluate_quality_gates(
|
|
762
|
+
security_findings, testing_findings, quality_findings, documentation_findings
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
# Determine approval status
|
|
766
|
+
blockers = []
|
|
767
|
+
warnings = []
|
|
768
|
+
recommendations = []
|
|
769
|
+
|
|
770
|
+
for gate in quality_gates:
|
|
771
|
+
if not gate.passed:
|
|
772
|
+
if gate.critical:
|
|
773
|
+
blockers.append(f"{gate.name} failed: {gate.message}")
|
|
774
|
+
else:
|
|
775
|
+
warnings.append(f"{gate.name} below threshold: {gate.message}")
|
|
776
|
+
|
|
777
|
+
approved = len(blockers) == 0
|
|
778
|
+
confidence = (
|
|
779
|
+
"high" if approved and len(warnings) == 0 else ("medium" if approved else "low")
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
# Calculate duration
|
|
783
|
+
duration_ms = int((datetime.now() - started_at).total_seconds() * 1000)
|
|
784
|
+
|
|
785
|
+
# Build result
|
|
786
|
+
result = ReleasePreparationCrewResult(
|
|
787
|
+
success=True,
|
|
788
|
+
approved=approved,
|
|
789
|
+
confidence=confidence,
|
|
790
|
+
quality_gates=quality_gates,
|
|
791
|
+
security_findings=security_findings,
|
|
792
|
+
testing_findings=testing_findings,
|
|
793
|
+
quality_findings=quality_findings,
|
|
794
|
+
documentation_findings=documentation_findings,
|
|
795
|
+
blockers=blockers,
|
|
796
|
+
warnings=warnings,
|
|
797
|
+
recommendations=recommendations,
|
|
798
|
+
cost=self._total_cost,
|
|
799
|
+
duration_ms=duration_ms,
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
# Add formatted report
|
|
803
|
+
print(result.formatted_report)
|
|
804
|
+
|
|
805
|
+
return result
|
|
806
|
+
|
|
807
|
+
def _parse_json_answer(self, answer: str) -> dict | None:
|
|
808
|
+
"""Parse JSON from agent answer, handling markdown code blocks.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
answer: Agent answer text (may contain ```json...```)
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
Parsed JSON dict or None if parsing fails
|
|
815
|
+
"""
|
|
816
|
+
import json
|
|
817
|
+
import re
|
|
818
|
+
|
|
819
|
+
try:
|
|
820
|
+
# Remove markdown code blocks if present
|
|
821
|
+
answer_cleaned = re.sub(r"```json\s*", "", answer)
|
|
822
|
+
answer_cleaned = re.sub(r"```\s*$", "", answer_cleaned)
|
|
823
|
+
answer_cleaned = answer_cleaned.strip()
|
|
824
|
+
|
|
825
|
+
# Parse as JSON
|
|
826
|
+
return json.loads(answer_cleaned)
|
|
827
|
+
except Exception:
|
|
828
|
+
return None
|
|
829
|
+
|
|
830
|
+
def _extract_security_findings(self, agent_finding: dict) -> dict:
|
|
831
|
+
"""Extract structured security findings from agent response."""
|
|
832
|
+
answer = agent_finding.get("answer", "")
|
|
833
|
+
data = self._parse_json_answer(answer)
|
|
834
|
+
|
|
835
|
+
if data:
|
|
836
|
+
return {
|
|
837
|
+
"critical_count": data.get("critical_issues_count", 0),
|
|
838
|
+
"high_count": data.get("high_issues_count", 0),
|
|
839
|
+
"findings": data.get("findings", []),
|
|
840
|
+
"recommendation": data.get("recommendation", "UNKNOWN"),
|
|
841
|
+
"summary": f"{data.get('critical_issues_count', 0)} critical, {data.get('high_issues_count', 0)} high issues found",
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
# Fallback
|
|
845
|
+
return {
|
|
846
|
+
"critical_count": 0,
|
|
847
|
+
"high_count": 0,
|
|
848
|
+
"findings": [],
|
|
849
|
+
"recommendation": "UNKNOWN",
|
|
850
|
+
"summary": "Could not parse security findings",
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
def _extract_testing_findings(self, agent_finding: dict) -> dict:
|
|
854
|
+
"""Extract structured testing findings from agent response."""
|
|
855
|
+
answer = agent_finding.get("answer", "")
|
|
856
|
+
data = self._parse_json_answer(answer)
|
|
857
|
+
|
|
858
|
+
if data:
|
|
859
|
+
return {
|
|
860
|
+
"coverage": data.get("coverage_percentage", 0.0),
|
|
861
|
+
"gaps_count": data.get("critical_gaps_count", 0),
|
|
862
|
+
"tests_passing": data.get("tests_passing", False),
|
|
863
|
+
"recommendation": data.get("recommendation", "UNKNOWN"),
|
|
864
|
+
"summary": f"{data.get('coverage_percentage', 0)}% coverage, {data.get('critical_gaps_count', 0)} critical gaps",
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return {
|
|
868
|
+
"coverage": 0.0,
|
|
869
|
+
"gaps_count": 0,
|
|
870
|
+
"tests_passing": False,
|
|
871
|
+
"recommendation": "UNKNOWN",
|
|
872
|
+
"summary": "Could not parse testing findings",
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
def _extract_quality_findings(self, agent_finding: dict) -> dict:
|
|
876
|
+
"""Extract structured quality findings from agent response."""
|
|
877
|
+
answer = agent_finding.get("answer", "")
|
|
878
|
+
data = self._parse_json_answer(answer)
|
|
879
|
+
|
|
880
|
+
if data:
|
|
881
|
+
return {
|
|
882
|
+
"quality_score": data.get("quality_score", 0.0),
|
|
883
|
+
"complexity_issues": data.get("complexity_issues", 0),
|
|
884
|
+
"linting_errors": data.get("linting_errors", 0),
|
|
885
|
+
"recommendation": data.get("recommendation", "UNKNOWN"),
|
|
886
|
+
"summary": f"Quality score: {data.get('quality_score', 0)}/10, {data.get('complexity_issues', 0)} complexity issues",
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
"quality_score": 0.0,
|
|
891
|
+
"complexity_issues": 0,
|
|
892
|
+
"linting_errors": 0,
|
|
893
|
+
"recommendation": "UNKNOWN",
|
|
894
|
+
"summary": "Could not parse quality findings",
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
def _extract_documentation_findings(self, agent_finding: dict) -> dict:
|
|
898
|
+
"""Extract structured documentation findings from agent response."""
|
|
899
|
+
answer = agent_finding.get("answer", "")
|
|
900
|
+
data = self._parse_json_answer(answer)
|
|
901
|
+
|
|
902
|
+
if data:
|
|
903
|
+
return {
|
|
904
|
+
"docstring_coverage": data.get("docstring_coverage_percentage", 0.0),
|
|
905
|
+
"readme_current": data.get("readme_current", False),
|
|
906
|
+
"missing_docs_count": data.get("missing_docs_count", 0),
|
|
907
|
+
"recommendation": data.get("recommendation", "UNKNOWN"),
|
|
908
|
+
"summary": f"{data.get('docstring_coverage_percentage', 0)}% docstring coverage, README {'current' if data.get('readme_current') else 'needs update'}",
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return {
|
|
912
|
+
"docstring_coverage": 0.0,
|
|
913
|
+
"readme_current": False,
|
|
914
|
+
"missing_docs_count": 0,
|
|
915
|
+
"recommendation": "UNKNOWN",
|
|
916
|
+
"summary": "Could not parse documentation findings",
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
def _evaluate_quality_gates(
|
|
920
|
+
self,
|
|
921
|
+
security_findings: dict,
|
|
922
|
+
testing_findings: dict,
|
|
923
|
+
quality_findings: dict,
|
|
924
|
+
documentation_findings: dict,
|
|
925
|
+
) -> list[QualityGate]:
|
|
926
|
+
"""Evaluate quality gates based on agent findings."""
|
|
927
|
+
gates = []
|
|
928
|
+
|
|
929
|
+
# Security gate: no critical issues
|
|
930
|
+
sec_gate = QualityGate(
|
|
931
|
+
name="Security",
|
|
932
|
+
threshold=self.quality_gates["security"],
|
|
933
|
+
actual=float(security_findings.get("critical_count", 0)),
|
|
934
|
+
critical=True,
|
|
935
|
+
)
|
|
936
|
+
sec_gate.passed = sec_gate.actual <= sec_gate.threshold
|
|
937
|
+
gates.append(sec_gate)
|
|
938
|
+
|
|
939
|
+
# Test coverage gate
|
|
940
|
+
cov_gate = QualityGate(
|
|
941
|
+
name="Test Coverage",
|
|
942
|
+
threshold=self.quality_gates["coverage"],
|
|
943
|
+
actual=testing_findings.get("coverage", 0.0),
|
|
944
|
+
critical=True,
|
|
945
|
+
)
|
|
946
|
+
cov_gate.passed = cov_gate.actual >= cov_gate.threshold
|
|
947
|
+
gates.append(cov_gate)
|
|
948
|
+
|
|
949
|
+
# Code quality gate
|
|
950
|
+
qual_gate = QualityGate(
|
|
951
|
+
name="Code Quality",
|
|
952
|
+
threshold=self.quality_gates["quality"],
|
|
953
|
+
actual=quality_findings.get("quality_score", 0.0),
|
|
954
|
+
critical=True,
|
|
955
|
+
)
|
|
956
|
+
qual_gate.passed = qual_gate.actual >= qual_gate.threshold
|
|
957
|
+
gates.append(qual_gate)
|
|
958
|
+
|
|
959
|
+
# Documentation gate
|
|
960
|
+
doc_gate = QualityGate(
|
|
961
|
+
name="Documentation",
|
|
962
|
+
threshold=self.quality_gates["documentation"],
|
|
963
|
+
actual=documentation_findings.get("docstring_coverage", 0.0),
|
|
964
|
+
critical=False, # Non-critical warning
|
|
965
|
+
)
|
|
966
|
+
doc_gate.passed = doc_gate.actual >= doc_gate.threshold
|
|
967
|
+
gates.append(doc_gate)
|
|
968
|
+
|
|
969
|
+
return gates
|