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,593 @@
|
|
|
1
|
+
"""Secure Release Pipeline
|
|
2
|
+
|
|
3
|
+
A comprehensive security pipeline that composes multiple security workflows
|
|
4
|
+
for maximum coverage before release approval.
|
|
5
|
+
|
|
6
|
+
Orchestrates:
|
|
7
|
+
1. SecurityAuditCrew (optional, parallel) - 5-agent multi-agent security crew
|
|
8
|
+
2. SecurityAuditWorkflow - OWASP-focused vulnerability scanning
|
|
9
|
+
3. CodeReviewWorkflow - Security-aware code review (optional)
|
|
10
|
+
4. ReleasePreparationWorkflow - Pre-release quality gate
|
|
11
|
+
|
|
12
|
+
Copyright 2025 Smart-AI-Memory
|
|
13
|
+
Licensed under Fair Source License 0.9
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from .base import WorkflowResult
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class SecureReleaseResult:
|
|
29
|
+
"""Result from SecureReleasePipeline execution."""
|
|
30
|
+
|
|
31
|
+
success: bool
|
|
32
|
+
go_no_go: str # "GO", "NO_GO", "CONDITIONAL"
|
|
33
|
+
|
|
34
|
+
# Individual workflow results
|
|
35
|
+
crew_report: dict | None = None
|
|
36
|
+
security_audit: WorkflowResult | None = None
|
|
37
|
+
code_review: WorkflowResult | None = None
|
|
38
|
+
release_prep: WorkflowResult | None = None
|
|
39
|
+
|
|
40
|
+
# Unified metrics
|
|
41
|
+
combined_risk_score: float = 0.0
|
|
42
|
+
total_findings: int = 0
|
|
43
|
+
critical_count: int = 0
|
|
44
|
+
high_count: int = 0
|
|
45
|
+
|
|
46
|
+
# Cost tracking
|
|
47
|
+
total_cost: float = 0.0
|
|
48
|
+
total_duration_ms: int = 0
|
|
49
|
+
|
|
50
|
+
# Recommendations
|
|
51
|
+
blockers: list[str] = field(default_factory=list)
|
|
52
|
+
warnings: list[str] = field(default_factory=list)
|
|
53
|
+
recommendations: list[str] = field(default_factory=list)
|
|
54
|
+
|
|
55
|
+
# Metadata
|
|
56
|
+
mode: str = "full"
|
|
57
|
+
crew_enabled: bool = False
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> dict:
|
|
60
|
+
"""Convert result to dictionary."""
|
|
61
|
+
return {
|
|
62
|
+
"success": self.success,
|
|
63
|
+
"go_no_go": self.go_no_go,
|
|
64
|
+
"combined_risk_score": self.combined_risk_score,
|
|
65
|
+
"total_findings": self.total_findings,
|
|
66
|
+
"critical_count": self.critical_count,
|
|
67
|
+
"high_count": self.high_count,
|
|
68
|
+
"total_cost": self.total_cost,
|
|
69
|
+
"total_duration_ms": self.total_duration_ms,
|
|
70
|
+
"blockers": self.blockers,
|
|
71
|
+
"warnings": self.warnings,
|
|
72
|
+
"recommendations": self.recommendations,
|
|
73
|
+
"mode": self.mode,
|
|
74
|
+
"crew_enabled": self.crew_enabled,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def formatted_report(self) -> str:
|
|
79
|
+
"""Generate human-readable report."""
|
|
80
|
+
return format_secure_release_report(self)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class SecureReleasePipeline:
|
|
84
|
+
"""Comprehensive security pipeline for release preparation.
|
|
85
|
+
|
|
86
|
+
This pipeline composes multiple security workflows to provide
|
|
87
|
+
maximum coverage before release approval.
|
|
88
|
+
|
|
89
|
+
Execution modes:
|
|
90
|
+
- "full": Run all workflows (SecurityAuditCrew + all workflows) [DEFAULT]
|
|
91
|
+
- "standard": Skip crew, run all workflows (fallback when crew unavailable)
|
|
92
|
+
|
|
93
|
+
Note: For quick release checks without full security audit, use the
|
|
94
|
+
ReleasePreparationWorkflow directly instead.
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
pipeline = SecureReleasePipeline(mode="full")
|
|
98
|
+
result = await pipeline.execute(
|
|
99
|
+
path="./src",
|
|
100
|
+
diff="...",
|
|
101
|
+
files_changed=[...]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result.go_no_go == "GO":
|
|
105
|
+
print("Ready for release!")
|
|
106
|
+
else:
|
|
107
|
+
for blocker in result.blockers:
|
|
108
|
+
print(f"BLOCKER: {blocker}")
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
name = "secure-release"
|
|
112
|
+
description = "Comprehensive security pipeline composing multiple workflows"
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
mode: str = "full", # "full" or "standard"
|
|
117
|
+
use_crew: bool | None = None, # Override mode's crew setting
|
|
118
|
+
parallel_crew: bool = True, # Run crew in parallel with first workflow
|
|
119
|
+
crew_config: dict | None = None,
|
|
120
|
+
**kwargs: Any,
|
|
121
|
+
):
|
|
122
|
+
"""Initialize secure release pipeline.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
mode: Execution mode - "full" (with crew, DEFAULT) or "standard" (skip crew)
|
|
126
|
+
use_crew: Override crew setting (None uses mode default: full=True, standard=False)
|
|
127
|
+
parallel_crew: Run SecurityAuditCrew in parallel with first workflow
|
|
128
|
+
crew_config: Configuration for SecurityAuditCrew
|
|
129
|
+
**kwargs: Additional arguments passed to child workflows
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
# Validate mode
|
|
133
|
+
if mode not in ("full", "standard"):
|
|
134
|
+
raise ValueError(f"Invalid mode '{mode}'. Must be 'full' or 'standard'.")
|
|
135
|
+
|
|
136
|
+
self.mode = mode
|
|
137
|
+
self.use_crew = use_crew if use_crew is not None else (mode == "full")
|
|
138
|
+
self.parallel_crew = parallel_crew
|
|
139
|
+
self.crew_config = crew_config or {}
|
|
140
|
+
self.kwargs = kwargs
|
|
141
|
+
|
|
142
|
+
async def execute(
|
|
143
|
+
self,
|
|
144
|
+
path: str = ".",
|
|
145
|
+
diff: str = "",
|
|
146
|
+
files_changed: list[str] | None = None,
|
|
147
|
+
since: str = "1 week ago",
|
|
148
|
+
**kwargs: Any,
|
|
149
|
+
) -> SecureReleaseResult:
|
|
150
|
+
"""Execute the secure release pipeline.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
path: Path to codebase to analyze
|
|
154
|
+
diff: Git diff for code review (optional)
|
|
155
|
+
files_changed: List of changed files (optional)
|
|
156
|
+
since: Period for changelog generation
|
|
157
|
+
**kwargs: Additional arguments
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
SecureReleaseResult with combined analysis
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
from .security_adapters import (
|
|
165
|
+
_check_crew_available,
|
|
166
|
+
_get_crew_audit,
|
|
167
|
+
crew_report_to_workflow_format,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
adapters_available = True
|
|
171
|
+
except ImportError:
|
|
172
|
+
adapters_available = False
|
|
173
|
+
_check_crew_available = lambda: False
|
|
174
|
+
_get_crew_audit = None
|
|
175
|
+
crew_report_to_workflow_format = None
|
|
176
|
+
|
|
177
|
+
started_at = datetime.now()
|
|
178
|
+
|
|
179
|
+
crew_report = None
|
|
180
|
+
security_result = None
|
|
181
|
+
code_review_result = None
|
|
182
|
+
release_result = None
|
|
183
|
+
|
|
184
|
+
total_cost = 0.0
|
|
185
|
+
blockers: list[str] = []
|
|
186
|
+
warnings: list[str] = []
|
|
187
|
+
recommendations: list[str] = []
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Step 1: SecurityAuditCrew (parallel or first)
|
|
191
|
+
crew_task = None
|
|
192
|
+
crew_enabled = self.use_crew and adapters_available and _check_crew_available()
|
|
193
|
+
|
|
194
|
+
if crew_enabled:
|
|
195
|
+
if self.parallel_crew:
|
|
196
|
+
# Start crew in parallel
|
|
197
|
+
crew_task = asyncio.create_task(_get_crew_audit(path, self.crew_config))
|
|
198
|
+
else:
|
|
199
|
+
# Run crew first, then proceed
|
|
200
|
+
crew_report_obj = await _get_crew_audit(path, self.crew_config)
|
|
201
|
+
if crew_report_obj:
|
|
202
|
+
crew_report = crew_report_to_workflow_format(crew_report_obj)
|
|
203
|
+
|
|
204
|
+
# Step 2: SecurityAuditWorkflow
|
|
205
|
+
from .security_audit import SecurityAuditWorkflow
|
|
206
|
+
|
|
207
|
+
security_workflow = SecurityAuditWorkflow(**self.kwargs)
|
|
208
|
+
security_result = await security_workflow.execute(path=path)
|
|
209
|
+
total_cost += security_result.cost_report.total_cost
|
|
210
|
+
|
|
211
|
+
# Collect crew results if running in parallel
|
|
212
|
+
if crew_task:
|
|
213
|
+
try:
|
|
214
|
+
crew_report_obj = await asyncio.wait_for(crew_task, timeout=300.0)
|
|
215
|
+
if crew_report_obj:
|
|
216
|
+
crew_report = crew_report_to_workflow_format(crew_report_obj)
|
|
217
|
+
except asyncio.TimeoutError:
|
|
218
|
+
logger.warning("SecurityAuditCrew timed out")
|
|
219
|
+
warnings.append("SecurityAuditCrew timed out - results not included")
|
|
220
|
+
|
|
221
|
+
# Step 3: CodeReviewWorkflow (if diff provided)
|
|
222
|
+
if diff:
|
|
223
|
+
from .code_review import CodeReviewWorkflow
|
|
224
|
+
|
|
225
|
+
code_workflow = CodeReviewWorkflow(**self.kwargs)
|
|
226
|
+
|
|
227
|
+
# Pass crew findings as external audit if available
|
|
228
|
+
code_input: dict = {
|
|
229
|
+
"diff": diff,
|
|
230
|
+
"files_changed": files_changed or [],
|
|
231
|
+
}
|
|
232
|
+
if crew_report:
|
|
233
|
+
code_input["external_audit_results"] = crew_report
|
|
234
|
+
|
|
235
|
+
code_review_result = await code_workflow.execute(**code_input)
|
|
236
|
+
total_cost += code_review_result.cost_report.total_cost
|
|
237
|
+
|
|
238
|
+
# Step 4: ReleasePreparationWorkflow
|
|
239
|
+
from .release_prep import ReleasePreparationWorkflow
|
|
240
|
+
|
|
241
|
+
release_workflow = ReleasePreparationWorkflow(**self.kwargs)
|
|
242
|
+
release_result = await release_workflow.execute(path=path, since=since)
|
|
243
|
+
total_cost += release_result.cost_report.total_cost
|
|
244
|
+
|
|
245
|
+
# Aggregate results
|
|
246
|
+
combined_risk_score = self._calculate_combined_risk(
|
|
247
|
+
crew_report,
|
|
248
|
+
security_result,
|
|
249
|
+
code_review_result,
|
|
250
|
+
release_result,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
findings = self._aggregate_findings(crew_report, security_result, code_review_result)
|
|
254
|
+
|
|
255
|
+
# Determine go/no-go
|
|
256
|
+
go_no_go = self._determine_go_no_go(combined_risk_score, findings, release_result)
|
|
257
|
+
|
|
258
|
+
blockers, warnings, recommendations = self._generate_recommendations(
|
|
259
|
+
crew_report,
|
|
260
|
+
security_result,
|
|
261
|
+
code_review_result,
|
|
262
|
+
release_result,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Secure release pipeline failed: {e}")
|
|
267
|
+
blockers.append(f"Pipeline failed: {e!s}")
|
|
268
|
+
go_no_go = "NO_GO"
|
|
269
|
+
combined_risk_score = 100.0
|
|
270
|
+
findings = {"critical": 0, "high": 0, "total": 0}
|
|
271
|
+
|
|
272
|
+
completed_at = datetime.now()
|
|
273
|
+
duration_ms = int((completed_at - started_at).total_seconds() * 1000)
|
|
274
|
+
|
|
275
|
+
return SecureReleaseResult(
|
|
276
|
+
success=go_no_go != "NO_GO",
|
|
277
|
+
go_no_go=go_no_go,
|
|
278
|
+
crew_report=crew_report,
|
|
279
|
+
security_audit=security_result,
|
|
280
|
+
code_review=code_review_result,
|
|
281
|
+
release_prep=release_result,
|
|
282
|
+
combined_risk_score=combined_risk_score,
|
|
283
|
+
total_findings=findings.get("total", 0),
|
|
284
|
+
critical_count=findings.get("critical", 0),
|
|
285
|
+
high_count=findings.get("high", 0),
|
|
286
|
+
total_cost=total_cost,
|
|
287
|
+
total_duration_ms=duration_ms,
|
|
288
|
+
blockers=blockers,
|
|
289
|
+
warnings=warnings,
|
|
290
|
+
recommendations=recommendations,
|
|
291
|
+
mode=self.mode,
|
|
292
|
+
crew_enabled=crew_report is not None,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _calculate_combined_risk(
|
|
296
|
+
self,
|
|
297
|
+
crew_report: dict | None,
|
|
298
|
+
security_result: WorkflowResult | None,
|
|
299
|
+
code_review_result: WorkflowResult | None,
|
|
300
|
+
release_result: WorkflowResult | None,
|
|
301
|
+
) -> float:
|
|
302
|
+
"""Calculate combined risk score from all sources."""
|
|
303
|
+
scores = []
|
|
304
|
+
weights = []
|
|
305
|
+
|
|
306
|
+
if crew_report:
|
|
307
|
+
scores.append(crew_report.get("risk_score", 0))
|
|
308
|
+
weights.append(1.5) # Crew gets higher weight
|
|
309
|
+
|
|
310
|
+
if security_result and security_result.final_output:
|
|
311
|
+
assessment = security_result.final_output.get("assessment", {})
|
|
312
|
+
scores.append(assessment.get("risk_score", 0))
|
|
313
|
+
weights.append(1.0)
|
|
314
|
+
|
|
315
|
+
if code_review_result and code_review_result.final_output:
|
|
316
|
+
security_score = code_review_result.final_output.get("security_score", 90)
|
|
317
|
+
# Convert to risk (100 - security_score)
|
|
318
|
+
scores.append(100 - security_score)
|
|
319
|
+
weights.append(0.8)
|
|
320
|
+
|
|
321
|
+
if not scores:
|
|
322
|
+
return 0.0
|
|
323
|
+
|
|
324
|
+
weighted_sum = sum(s * w for s, w in zip(scores, weights, strict=False))
|
|
325
|
+
return float(min(100.0, weighted_sum / sum(weights)))
|
|
326
|
+
|
|
327
|
+
def _aggregate_findings(
|
|
328
|
+
self,
|
|
329
|
+
crew_report: dict | None,
|
|
330
|
+
security_result: WorkflowResult | None,
|
|
331
|
+
code_review_result: WorkflowResult | None,
|
|
332
|
+
) -> dict:
|
|
333
|
+
"""Aggregate findings from all sources."""
|
|
334
|
+
critical = 0
|
|
335
|
+
high = 0
|
|
336
|
+
total = 0
|
|
337
|
+
|
|
338
|
+
if crew_report:
|
|
339
|
+
assessment = crew_report.get("assessment", {})
|
|
340
|
+
critical += len(assessment.get("critical_findings", []))
|
|
341
|
+
high += len(assessment.get("high_findings", []))
|
|
342
|
+
total += crew_report.get("finding_count", 0)
|
|
343
|
+
|
|
344
|
+
if security_result and security_result.final_output:
|
|
345
|
+
assessment = security_result.final_output.get("assessment", {})
|
|
346
|
+
severity = assessment.get("severity_breakdown", {})
|
|
347
|
+
critical = max(critical, severity.get("critical", 0))
|
|
348
|
+
high = max(high, severity.get("high", 0))
|
|
349
|
+
total = max(
|
|
350
|
+
total,
|
|
351
|
+
sum(severity.values()) if severity else 0,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if code_review_result and code_review_result.final_output:
|
|
355
|
+
if code_review_result.final_output.get("has_critical_issues"):
|
|
356
|
+
critical = max(critical, 1)
|
|
357
|
+
|
|
358
|
+
return {"critical": critical, "high": high, "total": total}
|
|
359
|
+
|
|
360
|
+
def _determine_go_no_go(
|
|
361
|
+
self,
|
|
362
|
+
risk_score: float,
|
|
363
|
+
findings: dict,
|
|
364
|
+
release_result: WorkflowResult | None,
|
|
365
|
+
) -> str:
|
|
366
|
+
"""Determine go/no-go decision."""
|
|
367
|
+
# Critical findings = immediate NO_GO
|
|
368
|
+
if findings.get("critical", 0) > 0:
|
|
369
|
+
return "NO_GO"
|
|
370
|
+
|
|
371
|
+
# Very high risk = NO_GO
|
|
372
|
+
if risk_score >= 75:
|
|
373
|
+
return "NO_GO"
|
|
374
|
+
|
|
375
|
+
# High findings or elevated risk = CONDITIONAL
|
|
376
|
+
if findings.get("high", 0) > 3 or risk_score >= 50:
|
|
377
|
+
return "CONDITIONAL"
|
|
378
|
+
|
|
379
|
+
# Release workflow not approved = CONDITIONAL
|
|
380
|
+
if release_result and release_result.final_output:
|
|
381
|
+
if not release_result.final_output.get("approved", True):
|
|
382
|
+
return "CONDITIONAL"
|
|
383
|
+
|
|
384
|
+
return "GO"
|
|
385
|
+
|
|
386
|
+
def _generate_recommendations(
|
|
387
|
+
self,
|
|
388
|
+
crew_report: dict | None,
|
|
389
|
+
security_result: WorkflowResult | None,
|
|
390
|
+
code_review_result: WorkflowResult | None,
|
|
391
|
+
release_result: WorkflowResult | None,
|
|
392
|
+
) -> tuple[list[str], list[str], list[str]]:
|
|
393
|
+
"""Generate blockers, warnings, and recommendations."""
|
|
394
|
+
blockers = []
|
|
395
|
+
warnings = []
|
|
396
|
+
recommendations = []
|
|
397
|
+
|
|
398
|
+
# Crew findings
|
|
399
|
+
if crew_report:
|
|
400
|
+
critical = crew_report.get("assessment", {}).get("critical_findings", [])
|
|
401
|
+
for f in critical:
|
|
402
|
+
blockers.append(f"Critical: {f.get('title', 'Unknown issue')}")
|
|
403
|
+
|
|
404
|
+
high = crew_report.get("assessment", {}).get("high_findings", [])
|
|
405
|
+
for f in high[:3]: # Top 3
|
|
406
|
+
warnings.append(f"High: {f.get('title', 'Unknown issue')}")
|
|
407
|
+
|
|
408
|
+
# Security audit findings
|
|
409
|
+
if security_result and security_result.final_output:
|
|
410
|
+
assessment = security_result.final_output.get("assessment", {})
|
|
411
|
+
if assessment.get("risk_level") == "critical":
|
|
412
|
+
blockers.append("Security audit identified critical risk level")
|
|
413
|
+
elif assessment.get("risk_level") == "high":
|
|
414
|
+
warnings.append("Security audit identified high risk level")
|
|
415
|
+
|
|
416
|
+
# Code review verdict
|
|
417
|
+
if code_review_result and code_review_result.final_output:
|
|
418
|
+
verdict = code_review_result.final_output.get("verdict", "")
|
|
419
|
+
if verdict == "reject":
|
|
420
|
+
blockers.append("Code review: Changes rejected")
|
|
421
|
+
elif verdict == "request_changes":
|
|
422
|
+
warnings.append("Code review: Changes requested")
|
|
423
|
+
|
|
424
|
+
# Release prep blockers
|
|
425
|
+
if release_result and release_result.final_output:
|
|
426
|
+
for b in release_result.final_output.get("blockers", []):
|
|
427
|
+
blockers.append(f"Release: {b}")
|
|
428
|
+
for w in release_result.final_output.get("warnings", []):
|
|
429
|
+
warnings.append(f"Release: {w}")
|
|
430
|
+
|
|
431
|
+
# General recommendations
|
|
432
|
+
if not blockers and not warnings:
|
|
433
|
+
recommendations.append("All checks passed - ready for release")
|
|
434
|
+
elif blockers:
|
|
435
|
+
recommendations.append("Address all blockers before release")
|
|
436
|
+
recommendations.append("Consider running security audit on fixes")
|
|
437
|
+
elif warnings:
|
|
438
|
+
recommendations.append("Review warnings before release")
|
|
439
|
+
recommendations.append("Document accepted risks if proceeding")
|
|
440
|
+
|
|
441
|
+
return blockers, warnings, recommendations
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def for_pr_review(cls, files_changed: int = 0) -> "SecureReleasePipeline":
|
|
445
|
+
"""Create pipeline optimized for PR review."""
|
|
446
|
+
return cls(
|
|
447
|
+
mode="standard" if files_changed < 10 else "full",
|
|
448
|
+
parallel_crew=True,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
@classmethod
|
|
452
|
+
def for_release(cls) -> "SecureReleasePipeline":
|
|
453
|
+
"""Create pipeline for release preparation."""
|
|
454
|
+
return cls(
|
|
455
|
+
mode="full",
|
|
456
|
+
crew_config={"scan_depth": "thorough"},
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def format_secure_release_report(result: SecureReleaseResult) -> str:
|
|
461
|
+
"""Format secure release result as a human-readable report.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
result: The SecureReleaseResult object
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Formatted report string
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
lines = []
|
|
471
|
+
|
|
472
|
+
# Header with go/no-go decision
|
|
473
|
+
go_no_go = result.go_no_go
|
|
474
|
+
|
|
475
|
+
if go_no_go == "GO":
|
|
476
|
+
status_icon = "✅"
|
|
477
|
+
status_text = "READY FOR RELEASE"
|
|
478
|
+
elif go_no_go == "CONDITIONAL":
|
|
479
|
+
status_icon = "⚠️"
|
|
480
|
+
status_text = "CONDITIONAL APPROVAL"
|
|
481
|
+
else:
|
|
482
|
+
status_icon = "❌"
|
|
483
|
+
status_text = "RELEASE BLOCKED"
|
|
484
|
+
|
|
485
|
+
lines.append("=" * 60)
|
|
486
|
+
lines.append("SECURE RELEASE REPORT")
|
|
487
|
+
lines.append("=" * 60)
|
|
488
|
+
lines.append("")
|
|
489
|
+
lines.append(f"Decision: {status_icon} {go_no_go} - {status_text}")
|
|
490
|
+
lines.append(f"Risk Score: {result.combined_risk_score:.1f}/100")
|
|
491
|
+
lines.append(f"Pipeline Mode: {result.mode.upper()}")
|
|
492
|
+
lines.append(f"Crew Enabled: {'Yes' if result.crew_enabled else 'No'}")
|
|
493
|
+
lines.append("")
|
|
494
|
+
|
|
495
|
+
# Findings summary
|
|
496
|
+
lines.append("-" * 60)
|
|
497
|
+
lines.append("FINDINGS SUMMARY")
|
|
498
|
+
lines.append("-" * 60)
|
|
499
|
+
lines.append(f"Total Findings: {result.total_findings}")
|
|
500
|
+
lines.append(f" 🔴 Critical: {result.critical_count}")
|
|
501
|
+
lines.append(f" 🟠 High: {result.high_count}")
|
|
502
|
+
lines.append("")
|
|
503
|
+
|
|
504
|
+
# Blockers
|
|
505
|
+
if result.blockers:
|
|
506
|
+
lines.append("-" * 60)
|
|
507
|
+
lines.append("🚫 BLOCKERS (Must Fix Before Release)")
|
|
508
|
+
lines.append("-" * 60)
|
|
509
|
+
for blocker in result.blockers:
|
|
510
|
+
lines.append(f" • {blocker}")
|
|
511
|
+
lines.append("")
|
|
512
|
+
|
|
513
|
+
# Warnings
|
|
514
|
+
if result.warnings:
|
|
515
|
+
lines.append("-" * 60)
|
|
516
|
+
lines.append("⚠️ WARNINGS")
|
|
517
|
+
lines.append("-" * 60)
|
|
518
|
+
for warning in result.warnings:
|
|
519
|
+
lines.append(f" • {warning}")
|
|
520
|
+
lines.append("")
|
|
521
|
+
|
|
522
|
+
# Recommendations
|
|
523
|
+
if result.recommendations:
|
|
524
|
+
lines.append("-" * 60)
|
|
525
|
+
lines.append("💡 RECOMMENDATIONS")
|
|
526
|
+
lines.append("-" * 60)
|
|
527
|
+
for rec in result.recommendations:
|
|
528
|
+
lines.append(f" • {rec}")
|
|
529
|
+
lines.append("")
|
|
530
|
+
|
|
531
|
+
# Individual workflow summaries
|
|
532
|
+
lines.append("-" * 60)
|
|
533
|
+
lines.append("WORKFLOW RESULTS")
|
|
534
|
+
lines.append("-" * 60)
|
|
535
|
+
|
|
536
|
+
# Crew results
|
|
537
|
+
if result.crew_report:
|
|
538
|
+
crew_risk = result.crew_report.get("risk_score", 0)
|
|
539
|
+
crew_findings = result.crew_report.get("finding_count", 0)
|
|
540
|
+
crew_icon = "✅" if crew_risk < 50 else "⚠️" if crew_risk < 75 else "❌"
|
|
541
|
+
lines.append(
|
|
542
|
+
f" {crew_icon} SecurityAuditCrew: {crew_findings} findings, risk {crew_risk}/100",
|
|
543
|
+
)
|
|
544
|
+
elif result.crew_enabled:
|
|
545
|
+
lines.append(" ⏭️ SecurityAuditCrew: Skipped or failed")
|
|
546
|
+
|
|
547
|
+
# Security audit
|
|
548
|
+
if result.security_audit:
|
|
549
|
+
sec_output = result.security_audit.final_output or {}
|
|
550
|
+
assessment = sec_output.get("assessment", {})
|
|
551
|
+
sec_risk = assessment.get("risk_score", 0)
|
|
552
|
+
sec_level = assessment.get("risk_level", "unknown")
|
|
553
|
+
sec_icon = "✅" if sec_risk < 50 else "⚠️" if sec_risk < 75 else "❌"
|
|
554
|
+
lines.append(f" {sec_icon} SecurityAudit: {sec_level} risk ({sec_risk}/100)")
|
|
555
|
+
|
|
556
|
+
# Code review
|
|
557
|
+
if result.code_review:
|
|
558
|
+
cr_output = result.code_review.final_output or {}
|
|
559
|
+
verdict = cr_output.get("verdict", "unknown")
|
|
560
|
+
cr_icon = "✅" if verdict == "approve" else "⚠️" if verdict == "request_changes" else "❌"
|
|
561
|
+
lines.append(f" {cr_icon} CodeReview: {verdict}")
|
|
562
|
+
|
|
563
|
+
# Release prep
|
|
564
|
+
if result.release_prep:
|
|
565
|
+
rp_output = result.release_prep.final_output or {}
|
|
566
|
+
approved = rp_output.get("approved", False)
|
|
567
|
+
confidence = rp_output.get("confidence", "unknown")
|
|
568
|
+
rp_icon = "✅" if approved else "❌"
|
|
569
|
+
lines.append(
|
|
570
|
+
f" {rp_icon} ReleasePrep: {'Approved' if approved else 'Not Approved'} ({confidence} confidence)",
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
lines.append("")
|
|
574
|
+
|
|
575
|
+
# Cost and duration
|
|
576
|
+
lines.append("-" * 60)
|
|
577
|
+
lines.append("EXECUTION DETAILS")
|
|
578
|
+
lines.append("-" * 60)
|
|
579
|
+
lines.append(f"Total Cost: ${result.total_cost:.4f}")
|
|
580
|
+
lines.append(f"Duration: {result.total_duration_ms:.0f}ms")
|
|
581
|
+
lines.append("")
|
|
582
|
+
|
|
583
|
+
# Footer
|
|
584
|
+
lines.append("=" * 60)
|
|
585
|
+
if go_no_go == "GO":
|
|
586
|
+
lines.append("All security checks passed. Proceed with release.")
|
|
587
|
+
elif go_no_go == "CONDITIONAL":
|
|
588
|
+
lines.append("Review warnings before proceeding with release.")
|
|
589
|
+
else:
|
|
590
|
+
lines.append("Address all blockers before release can proceed.")
|
|
591
|
+
lines.append("=" * 60)
|
|
592
|
+
|
|
593
|
+
return "\n".join(lines)
|