empathy-framework 3.2.3__py3-none-any.whl → 3.8.2__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.
- coach_wizards/__init__.py +11 -12
- coach_wizards/accessibility_wizard.py +12 -12
- coach_wizards/api_wizard.py +12 -12
- coach_wizards/base_wizard.py +26 -20
- coach_wizards/cicd_wizard.py +15 -13
- coach_wizards/code_reviewer_README.md +60 -0
- coach_wizards/code_reviewer_wizard.py +180 -0
- coach_wizards/compliance_wizard.py +12 -12
- coach_wizards/database_wizard.py +12 -12
- coach_wizards/debugging_wizard.py +12 -12
- coach_wizards/documentation_wizard.py +12 -12
- coach_wizards/generate_wizards.py +1 -2
- coach_wizards/localization_wizard.py +101 -19
- coach_wizards/migration_wizard.py +12 -12
- coach_wizards/monitoring_wizard.py +12 -12
- coach_wizards/observability_wizard.py +12 -12
- coach_wizards/performance_wizard.py +12 -12
- coach_wizards/prompt_engineering_wizard.py +22 -25
- coach_wizards/refactoring_wizard.py +12 -12
- coach_wizards/scaling_wizard.py +12 -12
- coach_wizards/security_wizard.py +12 -12
- coach_wizards/testing_wizard.py +12 -12
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/METADATA +513 -58
- empathy_framework-3.8.2.dist-info/RECORD +333 -0
- empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
- empathy_healthcare_plugin/__init__.py +1 -2
- empathy_healthcare_plugin/monitors/__init__.py +9 -0
- empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
- empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
- empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
- empathy_llm_toolkit/__init__.py +7 -7
- empathy_llm_toolkit/agent_factory/__init__.py +53 -0
- empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
- empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
- empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
- empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
- empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
- empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
- empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
- empathy_llm_toolkit/agent_factory/base.py +305 -0
- empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
- empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
- empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
- empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
- empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
- empathy_llm_toolkit/agent_factory/decorators.py +286 -0
- empathy_llm_toolkit/agent_factory/factory.py +558 -0
- empathy_llm_toolkit/agent_factory/framework.py +192 -0
- empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
- empathy_llm_toolkit/agent_factory/resilient.py +320 -0
- empathy_llm_toolkit/claude_memory.py +14 -15
- empathy_llm_toolkit/cli/__init__.py +8 -0
- empathy_llm_toolkit/cli/sync_claude.py +487 -0
- empathy_llm_toolkit/code_health.py +177 -22
- empathy_llm_toolkit/config/__init__.py +29 -0
- empathy_llm_toolkit/config/unified.py +295 -0
- empathy_llm_toolkit/contextual_patterns.py +11 -12
- empathy_llm_toolkit/core.py +51 -49
- empathy_llm_toolkit/git_pattern_extractor.py +16 -12
- empathy_llm_toolkit/levels.py +6 -13
- empathy_llm_toolkit/pattern_confidence.py +14 -18
- empathy_llm_toolkit/pattern_resolver.py +10 -12
- empathy_llm_toolkit/pattern_summary.py +13 -11
- empathy_llm_toolkit/providers.py +194 -28
- empathy_llm_toolkit/routing/__init__.py +32 -0
- empathy_llm_toolkit/routing/model_router.py +362 -0
- empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
- empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
- empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
- empathy_llm_toolkit/security/README.md +262 -0
- empathy_llm_toolkit/security/__init__.py +62 -0
- empathy_llm_toolkit/security/audit_logger.py +929 -0
- empathy_llm_toolkit/security/audit_logger_example.py +152 -0
- empathy_llm_toolkit/security/pii_scrubber.py +640 -0
- empathy_llm_toolkit/security/secrets_detector.py +678 -0
- empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
- empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
- empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
- empathy_llm_toolkit/session_status.py +18 -20
- empathy_llm_toolkit/state.py +20 -21
- empathy_llm_toolkit/wizards/__init__.py +38 -0
- empathy_llm_toolkit/wizards/base_wizard.py +364 -0
- empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
- empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
- empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
- empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
- empathy_os/__init__.py +76 -77
- empathy_os/adaptive/__init__.py +13 -0
- empathy_os/adaptive/task_complexity.py +127 -0
- empathy_os/{monitoring.py → agent_monitoring.py} +27 -27
- empathy_os/cache/__init__.py +117 -0
- empathy_os/cache/base.py +166 -0
- empathy_os/cache/dependency_manager.py +253 -0
- empathy_os/cache/hash_only.py +248 -0
- empathy_os/cache/hybrid.py +390 -0
- empathy_os/cache/storage.py +282 -0
- empathy_os/cli.py +515 -109
- empathy_os/cli_unified.py +189 -42
- empathy_os/config/__init__.py +63 -0
- empathy_os/config/xml_config.py +239 -0
- empathy_os/config.py +87 -36
- empathy_os/coordination.py +48 -54
- empathy_os/core.py +90 -99
- empathy_os/cost_tracker.py +20 -23
- empathy_os/dashboard/__init__.py +15 -0
- empathy_os/dashboard/server.py +743 -0
- empathy_os/discovery.py +9 -11
- empathy_os/emergence.py +20 -21
- empathy_os/exceptions.py +18 -30
- empathy_os/feedback_loops.py +27 -30
- empathy_os/levels.py +31 -34
- empathy_os/leverage_points.py +27 -28
- empathy_os/logging_config.py +11 -12
- empathy_os/memory/__init__.py +195 -0
- empathy_os/memory/claude_memory.py +466 -0
- empathy_os/memory/config.py +224 -0
- empathy_os/memory/control_panel.py +1298 -0
- empathy_os/memory/edges.py +179 -0
- empathy_os/memory/graph.py +567 -0
- empathy_os/memory/long_term.py +1194 -0
- empathy_os/memory/nodes.py +179 -0
- empathy_os/memory/redis_bootstrap.py +540 -0
- empathy_os/memory/security/__init__.py +31 -0
- empathy_os/memory/security/audit_logger.py +930 -0
- empathy_os/memory/security/pii_scrubber.py +640 -0
- empathy_os/memory/security/secrets_detector.py +678 -0
- empathy_os/memory/short_term.py +2119 -0
- empathy_os/memory/storage/__init__.py +15 -0
- empathy_os/memory/summary_index.py +583 -0
- empathy_os/memory/unified.py +619 -0
- empathy_os/metrics/__init__.py +12 -0
- empathy_os/metrics/prompt_metrics.py +190 -0
- empathy_os/models/__init__.py +136 -0
- empathy_os/models/__main__.py +13 -0
- empathy_os/models/cli.py +655 -0
- empathy_os/models/empathy_executor.py +354 -0
- empathy_os/models/executor.py +252 -0
- empathy_os/models/fallback.py +671 -0
- empathy_os/models/provider_config.py +563 -0
- empathy_os/models/registry.py +382 -0
- empathy_os/models/tasks.py +302 -0
- empathy_os/models/telemetry.py +548 -0
- empathy_os/models/token_estimator.py +378 -0
- empathy_os/models/validation.py +274 -0
- empathy_os/monitoring/__init__.py +52 -0
- empathy_os/monitoring/alerts.py +23 -0
- empathy_os/monitoring/alerts_cli.py +268 -0
- empathy_os/monitoring/multi_backend.py +271 -0
- empathy_os/monitoring/otel_backend.py +363 -0
- empathy_os/optimization/__init__.py +19 -0
- empathy_os/optimization/context_optimizer.py +272 -0
- empathy_os/pattern_library.py +29 -28
- empathy_os/persistence.py +30 -34
- empathy_os/platform_utils.py +261 -0
- empathy_os/plugins/__init__.py +28 -0
- empathy_os/plugins/base.py +361 -0
- empathy_os/plugins/registry.py +268 -0
- empathy_os/project_index/__init__.py +30 -0
- empathy_os/project_index/cli.py +335 -0
- empathy_os/project_index/crew_integration.py +430 -0
- empathy_os/project_index/index.py +425 -0
- empathy_os/project_index/models.py +501 -0
- empathy_os/project_index/reports.py +473 -0
- empathy_os/project_index/scanner.py +538 -0
- empathy_os/prompts/__init__.py +61 -0
- empathy_os/prompts/config.py +77 -0
- empathy_os/prompts/context.py +177 -0
- empathy_os/prompts/parser.py +285 -0
- empathy_os/prompts/registry.py +313 -0
- empathy_os/prompts/templates.py +208 -0
- empathy_os/redis_config.py +144 -58
- empathy_os/redis_memory.py +53 -56
- empathy_os/resilience/__init__.py +56 -0
- empathy_os/resilience/circuit_breaker.py +256 -0
- empathy_os/resilience/fallback.py +179 -0
- empathy_os/resilience/health.py +300 -0
- empathy_os/resilience/retry.py +209 -0
- empathy_os/resilience/timeout.py +135 -0
- empathy_os/routing/__init__.py +43 -0
- empathy_os/routing/chain_executor.py +433 -0
- empathy_os/routing/classifier.py +217 -0
- empathy_os/routing/smart_router.py +234 -0
- empathy_os/routing/wizard_registry.py +307 -0
- empathy_os/templates.py +12 -11
- empathy_os/trust/__init__.py +28 -0
- empathy_os/trust/circuit_breaker.py +579 -0
- empathy_os/trust_building.py +44 -36
- empathy_os/validation/__init__.py +19 -0
- empathy_os/validation/xml_validator.py +281 -0
- empathy_os/wizard_factory_cli.py +170 -0
- empathy_os/{workflows.py → workflow_commands.py} +123 -31
- empathy_os/workflows/__init__.py +360 -0
- empathy_os/workflows/base.py +1660 -0
- empathy_os/workflows/bug_predict.py +962 -0
- empathy_os/workflows/code_review.py +960 -0
- empathy_os/workflows/code_review_adapters.py +310 -0
- empathy_os/workflows/code_review_pipeline.py +720 -0
- empathy_os/workflows/config.py +600 -0
- empathy_os/workflows/dependency_check.py +648 -0
- empathy_os/workflows/document_gen.py +1069 -0
- empathy_os/workflows/documentation_orchestrator.py +1205 -0
- empathy_os/workflows/health_check.py +679 -0
- empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
- empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
- empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
- empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
- empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
- empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
- empathy_os/workflows/manage_documentation.py +804 -0
- empathy_os/workflows/new_sample_workflow1.py +146 -0
- empathy_os/workflows/new_sample_workflow1_README.md +150 -0
- empathy_os/workflows/perf_audit.py +687 -0
- empathy_os/workflows/pr_review.py +748 -0
- empathy_os/workflows/progress.py +445 -0
- empathy_os/workflows/progress_server.py +322 -0
- empathy_os/workflows/refactor_plan.py +693 -0
- empathy_os/workflows/release_prep.py +808 -0
- empathy_os/workflows/research_synthesis.py +404 -0
- empathy_os/workflows/secure_release.py +585 -0
- empathy_os/workflows/security_adapters.py +297 -0
- empathy_os/workflows/security_audit.py +1046 -0
- empathy_os/workflows/step_config.py +234 -0
- empathy_os/workflows/test5.py +125 -0
- empathy_os/workflows/test5_README.md +158 -0
- empathy_os/workflows/test_gen.py +1855 -0
- empathy_os/workflows/test_lifecycle.py +526 -0
- empathy_os/workflows/test_maintenance.py +626 -0
- empathy_os/workflows/test_maintenance_cli.py +590 -0
- empathy_os/workflows/test_maintenance_crew.py +821 -0
- empathy_os/workflows/xml_enhanced_crew.py +285 -0
- empathy_software_plugin/__init__.py +1 -2
- empathy_software_plugin/cli/__init__.py +120 -0
- empathy_software_plugin/cli/inspect.py +362 -0
- empathy_software_plugin/cli.py +35 -26
- empathy_software_plugin/plugin.py +4 -8
- empathy_software_plugin/wizards/__init__.py +42 -0
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
- empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
- empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
- empathy_software_plugin/wizards/base_wizard.py +288 -0
- empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
- empathy_software_plugin/wizards/code_review_wizard.py +606 -0
- empathy_software_plugin/wizards/debugging/__init__.py +50 -0
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
- empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
- empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
- empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
- empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
- empathy_software_plugin/wizards/debugging/verification.py +369 -0
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
- empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
- empathy_software_plugin/wizards/performance/__init__.py +9 -0
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
- empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
- empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
- empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
- empathy_software_plugin/wizards/security/__init__.py +32 -0
- empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
- empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
- empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
- empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
- empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
- empathy_software_plugin/wizards/testing/__init__.py +27 -0
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
- empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
- empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
- empathy_software_plugin/wizards/testing_wizard.py +274 -0
- hot_reload/README.md +473 -0
- hot_reload/__init__.py +62 -0
- hot_reload/config.py +84 -0
- hot_reload/integration.py +228 -0
- hot_reload/reloader.py +298 -0
- hot_reload/watcher.py +179 -0
- hot_reload/websocket.py +176 -0
- scaffolding/README.md +589 -0
- scaffolding/__init__.py +35 -0
- scaffolding/__main__.py +14 -0
- scaffolding/cli.py +240 -0
- test_generator/__init__.py +38 -0
- test_generator/__main__.py +14 -0
- test_generator/cli.py +226 -0
- test_generator/generator.py +325 -0
- test_generator/risk_analyzer.py +216 -0
- workflow_patterns/__init__.py +33 -0
- workflow_patterns/behavior.py +249 -0
- workflow_patterns/core.py +76 -0
- workflow_patterns/output.py +99 -0
- workflow_patterns/registry.py +255 -0
- workflow_patterns/structural.py +288 -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
- agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
- agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
- agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
- agents/compliance_anticipation_agent.py +0 -1427
- agents/epic_integration_wizard.py +0 -541
- agents/trust_building_behaviors.py +0 -891
- empathy_framework-3.2.3.dist-info/RECORD +0 -104
- empathy_framework-3.2.3.dist-info/entry_points.txt +0 -7
- empathy_llm_toolkit/htmlcov/status.json +0 -1
- empathy_llm_toolkit/security/htmlcov/status.json +0 -1
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
- {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
"""Health Check Workflow
|
|
2
|
+
|
|
3
|
+
A workflow wrapper for HealthCheckCrew that provides project health
|
|
4
|
+
diagnosis and fixing capabilities.
|
|
5
|
+
|
|
6
|
+
Uses XML-enhanced prompts and 5 specialized agents:
|
|
7
|
+
1. Health Lead - Coordinator
|
|
8
|
+
2. Lint Fixer - Ruff analysis and fixes
|
|
9
|
+
3. Type Resolver - Mypy analysis
|
|
10
|
+
4. Test Doctor - Pytest analysis
|
|
11
|
+
5. Dep Auditor - Dependency security
|
|
12
|
+
|
|
13
|
+
Copyright 2025 Smart-AI-Memory
|
|
14
|
+
Licensed under Fair Source License 0.9
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .base import BaseWorkflow, ModelTier
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class HealthCheckResult:
|
|
32
|
+
"""Result from HealthCheckWorkflow execution."""
|
|
33
|
+
|
|
34
|
+
success: bool
|
|
35
|
+
health_score: float
|
|
36
|
+
is_healthy: bool
|
|
37
|
+
issues: list[dict]
|
|
38
|
+
fixes: list[dict]
|
|
39
|
+
checks_run: dict[str, Any]
|
|
40
|
+
agents_used: list[str]
|
|
41
|
+
critical_count: int
|
|
42
|
+
high_count: int
|
|
43
|
+
applied_fixes_count: int
|
|
44
|
+
duration_seconds: float
|
|
45
|
+
cost: float
|
|
46
|
+
metadata: dict = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HealthCheckWorkflow(BaseWorkflow):
|
|
50
|
+
"""Workflow wrapper for HealthCheckCrew.
|
|
51
|
+
|
|
52
|
+
Provides comprehensive project health diagnosis and fixing using
|
|
53
|
+
5 specialized agents with XML-enhanced prompts.
|
|
54
|
+
|
|
55
|
+
Checks:
|
|
56
|
+
- Lint (ruff) - Code style and quality
|
|
57
|
+
- Types (mypy) - Type safety
|
|
58
|
+
- Tests (pytest) - Test suite health
|
|
59
|
+
- Dependencies - Security vulnerabilities and outdated packages
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
workflow = HealthCheckWorkflow()
|
|
63
|
+
result = await workflow.execute(path=".", auto_fix=True)
|
|
64
|
+
|
|
65
|
+
if result.is_healthy:
|
|
66
|
+
print("Project is healthy!")
|
|
67
|
+
else:
|
|
68
|
+
print(f"Health Score: {result.health_score}/100")
|
|
69
|
+
for issue in result.issues:
|
|
70
|
+
print(f" - {issue['title']}")
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
name = "health-check"
|
|
74
|
+
description = "Project health diagnosis and fixing with 5-agent crew"
|
|
75
|
+
|
|
76
|
+
stages = ["diagnose", "fix"]
|
|
77
|
+
tier_map = {
|
|
78
|
+
"diagnose": ModelTier.CAPABLE,
|
|
79
|
+
"fix": ModelTier.CAPABLE,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
auto_fix: bool = False,
|
|
85
|
+
check_lint: bool = True,
|
|
86
|
+
check_types: bool = True,
|
|
87
|
+
check_tests: bool = True,
|
|
88
|
+
check_deps: bool = True,
|
|
89
|
+
xml_prompts: bool = True,
|
|
90
|
+
**kwargs: Any,
|
|
91
|
+
):
|
|
92
|
+
"""Initialize health check workflow.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
auto_fix: Automatically apply safe fixes
|
|
96
|
+
check_lint: Run lint checks
|
|
97
|
+
check_types: Run type checks
|
|
98
|
+
check_tests: Run test checks
|
|
99
|
+
check_deps: Run dependency checks
|
|
100
|
+
xml_prompts: Use XML-enhanced prompts
|
|
101
|
+
**kwargs: Additional arguments passed to BaseWorkflow
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
super().__init__(**kwargs)
|
|
105
|
+
self.auto_fix = auto_fix
|
|
106
|
+
self.check_lint = check_lint
|
|
107
|
+
self.check_types = check_types
|
|
108
|
+
self.check_tests = check_tests
|
|
109
|
+
self.check_deps = check_deps
|
|
110
|
+
self.xml_prompts = xml_prompts
|
|
111
|
+
self._crew: Any = None
|
|
112
|
+
self._crew_available = False
|
|
113
|
+
|
|
114
|
+
def _check_crew_available(self) -> bool:
|
|
115
|
+
"""Check if HealthCheckCrew is available."""
|
|
116
|
+
import importlib.util
|
|
117
|
+
|
|
118
|
+
return importlib.util.find_spec("empathy_llm_toolkit.agent_factory.crews") is not None
|
|
119
|
+
|
|
120
|
+
async def _initialize_crew(self) -> None:
|
|
121
|
+
"""Initialize the HealthCheckCrew."""
|
|
122
|
+
if self._crew is not None:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
from empathy_llm_toolkit.agent_factory.crews import HealthCheckConfig, HealthCheckCrew
|
|
127
|
+
|
|
128
|
+
config = HealthCheckConfig(
|
|
129
|
+
check_lint=self.check_lint,
|
|
130
|
+
check_types=self.check_types,
|
|
131
|
+
check_tests=self.check_tests,
|
|
132
|
+
check_deps=self.check_deps,
|
|
133
|
+
auto_fix=self.auto_fix,
|
|
134
|
+
xml_prompts_enabled=self.xml_prompts,
|
|
135
|
+
)
|
|
136
|
+
self._crew = HealthCheckCrew(config=config)
|
|
137
|
+
self._crew_available = True
|
|
138
|
+
logger.info("HealthCheckCrew initialized successfully")
|
|
139
|
+
except ImportError as e:
|
|
140
|
+
logger.warning(f"HealthCheckCrew not available: {e}")
|
|
141
|
+
self._crew_available = False
|
|
142
|
+
|
|
143
|
+
async def run_stage(
|
|
144
|
+
self,
|
|
145
|
+
stage_name: str,
|
|
146
|
+
tier: ModelTier,
|
|
147
|
+
input_data: Any,
|
|
148
|
+
) -> tuple[Any, int, int]:
|
|
149
|
+
"""Route to specific stage implementation."""
|
|
150
|
+
if stage_name == "diagnose":
|
|
151
|
+
return await self._diagnose(input_data, tier)
|
|
152
|
+
if stage_name == "fix":
|
|
153
|
+
return await self._fix(input_data, tier)
|
|
154
|
+
raise ValueError(f"Unknown stage: {stage_name}")
|
|
155
|
+
|
|
156
|
+
async def _diagnose(self, input_data: dict, tier: ModelTier) -> tuple[dict, int, int]:
|
|
157
|
+
"""Run health diagnosis using HealthCheckCrew.
|
|
158
|
+
|
|
159
|
+
Falls back to basic checks if crew not available.
|
|
160
|
+
"""
|
|
161
|
+
path = input_data.get("path", ".")
|
|
162
|
+
|
|
163
|
+
# Initialize crew
|
|
164
|
+
await self._initialize_crew()
|
|
165
|
+
|
|
166
|
+
if self._crew_available and self._crew:
|
|
167
|
+
# Run crew-based health check
|
|
168
|
+
report = await self._crew.check(
|
|
169
|
+
path=path,
|
|
170
|
+
auto_fix=False, # Don't auto-fix in diagnose stage
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
result = {
|
|
174
|
+
"health_score": report.health_score,
|
|
175
|
+
"is_healthy": report.is_healthy,
|
|
176
|
+
"issues": [i.to_dict() for i in report.issues],
|
|
177
|
+
"checks_run": report.checks_run,
|
|
178
|
+
"agents_used": report.agents_used,
|
|
179
|
+
"crew_available": True,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
input_tokens = 500 # Estimate
|
|
183
|
+
output_tokens = len(str(result)) // 4
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
{
|
|
187
|
+
"diagnosis": result,
|
|
188
|
+
**input_data,
|
|
189
|
+
},
|
|
190
|
+
input_tokens,
|
|
191
|
+
output_tokens,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Fallback to basic checks without crew
|
|
195
|
+
result = await self._basic_health_check(path)
|
|
196
|
+
result["crew_available"] = False
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
{
|
|
200
|
+
"diagnosis": result,
|
|
201
|
+
**input_data,
|
|
202
|
+
},
|
|
203
|
+
100,
|
|
204
|
+
len(str(result)) // 4,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
async def _basic_health_check(self, path: str) -> dict:
|
|
208
|
+
"""Basic health check without crew (fallback)."""
|
|
209
|
+
import subprocess
|
|
210
|
+
|
|
211
|
+
issues = []
|
|
212
|
+
checks_run: dict[str, dict[str, Any]] = {}
|
|
213
|
+
health_score = 100.0
|
|
214
|
+
|
|
215
|
+
# Lint check
|
|
216
|
+
if self.check_lint:
|
|
217
|
+
try:
|
|
218
|
+
result = subprocess.run(
|
|
219
|
+
["python", "-m", "ruff", "check", path],
|
|
220
|
+
check=False,
|
|
221
|
+
capture_output=True,
|
|
222
|
+
text=True,
|
|
223
|
+
timeout=60,
|
|
224
|
+
)
|
|
225
|
+
lint_errors = result.stdout.count("\n")
|
|
226
|
+
checks_run["lint"] = {"passed": result.returncode == 0}
|
|
227
|
+
if result.returncode != 0:
|
|
228
|
+
health_score -= min(20, lint_errors)
|
|
229
|
+
issues.append(
|
|
230
|
+
{
|
|
231
|
+
"title": f"Lint: {lint_errors} issues found",
|
|
232
|
+
"category": "lint",
|
|
233
|
+
"severity": "medium",
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
except subprocess.TimeoutExpired:
|
|
237
|
+
logger.warning("Lint check timed out after 60s")
|
|
238
|
+
checks_run["lint"] = {"passed": True, "skipped": True, "reason": "timeout"}
|
|
239
|
+
except FileNotFoundError:
|
|
240
|
+
logger.info("Ruff not installed, skipping lint check")
|
|
241
|
+
checks_run["lint"] = {"passed": True, "skipped": True, "reason": "tool_missing"}
|
|
242
|
+
except subprocess.SubprocessError as e:
|
|
243
|
+
logger.error(f"Lint check subprocess error: {e}")
|
|
244
|
+
checks_run["lint"] = {"passed": True, "skipped": True, "reason": "subprocess_error"}
|
|
245
|
+
except Exception:
|
|
246
|
+
# INTENTIONAL: Graceful degradation - health checks are best-effort
|
|
247
|
+
logger.exception("Unexpected error in lint check")
|
|
248
|
+
checks_run["lint"] = {"passed": True, "skipped": True, "reason": "unexpected_error"}
|
|
249
|
+
|
|
250
|
+
# Type check
|
|
251
|
+
if self.check_types:
|
|
252
|
+
try:
|
|
253
|
+
result = subprocess.run(
|
|
254
|
+
["python", "-m", "mypy", path, "--ignore-missing-imports"],
|
|
255
|
+
check=False,
|
|
256
|
+
capture_output=True,
|
|
257
|
+
text=True,
|
|
258
|
+
timeout=120,
|
|
259
|
+
)
|
|
260
|
+
type_errors = result.stdout.count("error:")
|
|
261
|
+
checks_run["types"] = {"passed": result.returncode == 0}
|
|
262
|
+
if result.returncode != 0:
|
|
263
|
+
health_score -= min(20, type_errors * 2)
|
|
264
|
+
issues.append(
|
|
265
|
+
{
|
|
266
|
+
"title": f"Types: {type_errors} errors found",
|
|
267
|
+
"category": "types",
|
|
268
|
+
"severity": "medium",
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
except subprocess.TimeoutExpired:
|
|
272
|
+
logger.warning("Type check timed out after 120s")
|
|
273
|
+
checks_run["types"] = {"passed": True, "skipped": True, "reason": "timeout"}
|
|
274
|
+
except FileNotFoundError:
|
|
275
|
+
logger.info("Mypy not installed, skipping type check")
|
|
276
|
+
checks_run["types"] = {"passed": True, "skipped": True, "reason": "tool_missing"}
|
|
277
|
+
except subprocess.SubprocessError as e:
|
|
278
|
+
logger.error(f"Type check subprocess error: {e}")
|
|
279
|
+
checks_run["types"] = {
|
|
280
|
+
"passed": True,
|
|
281
|
+
"skipped": True,
|
|
282
|
+
"reason": "subprocess_error",
|
|
283
|
+
}
|
|
284
|
+
except Exception:
|
|
285
|
+
# INTENTIONAL: Graceful degradation - health checks are best-effort
|
|
286
|
+
logger.exception("Unexpected error in type check")
|
|
287
|
+
checks_run["types"] = {
|
|
288
|
+
"passed": True,
|
|
289
|
+
"skipped": True,
|
|
290
|
+
"reason": "unexpected_error",
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Test check
|
|
294
|
+
if self.check_tests:
|
|
295
|
+
try:
|
|
296
|
+
# Use "tests/" directory or rely on pytest.ini testpaths
|
|
297
|
+
# Don't pass path="." which overrides testpaths and causes collection errors
|
|
298
|
+
result = subprocess.run(
|
|
299
|
+
["python", "-m", "pytest", "tests/", "-q", "--tb=no", "--no-cov"],
|
|
300
|
+
check=False,
|
|
301
|
+
capture_output=True,
|
|
302
|
+
text=True,
|
|
303
|
+
timeout=180,
|
|
304
|
+
)
|
|
305
|
+
checks_run["tests"] = {"passed": result.returncode == 0}
|
|
306
|
+
if result.returncode != 0:
|
|
307
|
+
health_score -= 25
|
|
308
|
+
issues.append(
|
|
309
|
+
{
|
|
310
|
+
"title": "Tests: Some tests failing",
|
|
311
|
+
"category": "tests",
|
|
312
|
+
"severity": "high",
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
except subprocess.TimeoutExpired:
|
|
316
|
+
logger.warning("Test check timed out after 180s")
|
|
317
|
+
checks_run["tests"] = {"passed": True, "skipped": True, "reason": "timeout"}
|
|
318
|
+
except FileNotFoundError:
|
|
319
|
+
logger.info("Pytest not installed, skipping test check")
|
|
320
|
+
checks_run["tests"] = {"passed": True, "skipped": True, "reason": "tool_missing"}
|
|
321
|
+
except subprocess.SubprocessError as e:
|
|
322
|
+
logger.error(f"Test check subprocess error: {e}")
|
|
323
|
+
checks_run["tests"] = {
|
|
324
|
+
"passed": True,
|
|
325
|
+
"skipped": True,
|
|
326
|
+
"reason": "subprocess_error",
|
|
327
|
+
}
|
|
328
|
+
except Exception:
|
|
329
|
+
# INTENTIONAL: Graceful degradation - health checks are best-effort
|
|
330
|
+
logger.exception("Unexpected error in test check")
|
|
331
|
+
checks_run["tests"] = {
|
|
332
|
+
"passed": True,
|
|
333
|
+
"skipped": True,
|
|
334
|
+
"reason": "unexpected_error",
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
"health_score": max(0, health_score),
|
|
339
|
+
"is_healthy": health_score >= 80,
|
|
340
|
+
"issues": issues,
|
|
341
|
+
"checks_run": checks_run,
|
|
342
|
+
"agents_used": [],
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async def _fix(self, input_data: dict, tier: ModelTier) -> tuple[dict, int, int]:
|
|
346
|
+
"""Apply fixes for identified issues.
|
|
347
|
+
|
|
348
|
+
Only runs if auto_fix is enabled and issues were found.
|
|
349
|
+
"""
|
|
350
|
+
path = input_data.get("path", ".")
|
|
351
|
+
fixes = []
|
|
352
|
+
|
|
353
|
+
if not self.auto_fix:
|
|
354
|
+
return (
|
|
355
|
+
{
|
|
356
|
+
"fixes": [],
|
|
357
|
+
"auto_fix_enabled": False,
|
|
358
|
+
**input_data,
|
|
359
|
+
},
|
|
360
|
+
0,
|
|
361
|
+
0,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Use crew if available
|
|
365
|
+
if self._crew_available and self._crew:
|
|
366
|
+
report = await self._crew.check(
|
|
367
|
+
path=path,
|
|
368
|
+
auto_fix=True,
|
|
369
|
+
)
|
|
370
|
+
fixes = [f.to_dict() for f in report.fixes]
|
|
371
|
+
# Basic auto-fix with ruff
|
|
372
|
+
elif self.check_lint:
|
|
373
|
+
import subprocess
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
result = subprocess.run(
|
|
377
|
+
["python", "-m", "ruff", "check", path, "--fix"],
|
|
378
|
+
check=False,
|
|
379
|
+
capture_output=True,
|
|
380
|
+
text=True,
|
|
381
|
+
timeout=60,
|
|
382
|
+
)
|
|
383
|
+
if "fixed" in result.stdout.lower():
|
|
384
|
+
fixes.append(
|
|
385
|
+
{
|
|
386
|
+
"title": "Lint auto-fixes applied",
|
|
387
|
+
"category": "lint",
|
|
388
|
+
"status": "applied",
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
except subprocess.TimeoutExpired:
|
|
392
|
+
logger.warning("Ruff auto-fix timed out after 60s")
|
|
393
|
+
except FileNotFoundError:
|
|
394
|
+
logger.info("Ruff not installed, skipping auto-fix")
|
|
395
|
+
except subprocess.SubprocessError as e:
|
|
396
|
+
logger.error(f"Ruff auto-fix subprocess error: {e}")
|
|
397
|
+
except Exception:
|
|
398
|
+
# INTENTIONAL: Graceful degradation - auto-fix is best-effort
|
|
399
|
+
logger.exception("Unexpected error in ruff auto-fix")
|
|
400
|
+
|
|
401
|
+
return (
|
|
402
|
+
{
|
|
403
|
+
"fixes": fixes,
|
|
404
|
+
"auto_fix_enabled": True,
|
|
405
|
+
**input_data,
|
|
406
|
+
},
|
|
407
|
+
100,
|
|
408
|
+
len(str(fixes)) // 4,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
async def execute(self, **kwargs: Any) -> HealthCheckResult: # type: ignore[override]
|
|
412
|
+
"""Execute the health check workflow.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
path: Path to check (default: ".")
|
|
416
|
+
auto_fix: Override auto_fix setting
|
|
417
|
+
**kwargs: Additional arguments
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
HealthCheckResult with health score and findings
|
|
421
|
+
|
|
422
|
+
"""
|
|
423
|
+
start_time = time.time()
|
|
424
|
+
|
|
425
|
+
# Override auto_fix if provided
|
|
426
|
+
if "auto_fix" in kwargs:
|
|
427
|
+
self.auto_fix = kwargs.pop("auto_fix")
|
|
428
|
+
|
|
429
|
+
# Run base workflow
|
|
430
|
+
result = await super().execute(**kwargs)
|
|
431
|
+
|
|
432
|
+
duration = time.time() - start_time
|
|
433
|
+
final_output = result.final_output or {}
|
|
434
|
+
diagnosis = final_output.get("diagnosis", {})
|
|
435
|
+
fixes = final_output.get("fixes", [])
|
|
436
|
+
|
|
437
|
+
# Count severities
|
|
438
|
+
issues = diagnosis.get("issues", [])
|
|
439
|
+
critical_count = sum(1 for i in issues if i.get("severity") == "critical")
|
|
440
|
+
high_count = sum(1 for i in issues if i.get("severity") == "high")
|
|
441
|
+
applied_count = sum(1 for f in fixes if f.get("status") == "applied")
|
|
442
|
+
|
|
443
|
+
health_result = HealthCheckResult(
|
|
444
|
+
success=result.success,
|
|
445
|
+
health_score=diagnosis.get("health_score", 0),
|
|
446
|
+
is_healthy=diagnosis.get("is_healthy", False),
|
|
447
|
+
issues=issues,
|
|
448
|
+
fixes=fixes,
|
|
449
|
+
checks_run=diagnosis.get("checks_run", {}),
|
|
450
|
+
agents_used=diagnosis.get("agents_used", []),
|
|
451
|
+
critical_count=critical_count,
|
|
452
|
+
high_count=high_count,
|
|
453
|
+
applied_fixes_count=applied_count,
|
|
454
|
+
duration_seconds=duration,
|
|
455
|
+
cost=result.cost_report.total_cost,
|
|
456
|
+
metadata={
|
|
457
|
+
"crew_available": diagnosis.get("crew_available", False),
|
|
458
|
+
"auto_fix": self.auto_fix,
|
|
459
|
+
"xml_prompts": self.xml_prompts,
|
|
460
|
+
},
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Auto-save to .empathy/health.json for dashboard
|
|
464
|
+
self._save_health_data(health_result, kwargs.get("path", "."))
|
|
465
|
+
|
|
466
|
+
# Add formatted report to metadata for human readability
|
|
467
|
+
health_result.metadata["formatted_report"] = format_health_check_report(health_result)
|
|
468
|
+
|
|
469
|
+
return health_result
|
|
470
|
+
|
|
471
|
+
def _save_health_data(self, result: HealthCheckResult, project_path: str) -> None:
|
|
472
|
+
"""Save health check results to .empathy/health.json for dashboard."""
|
|
473
|
+
try:
|
|
474
|
+
# Determine empathy dir relative to project path
|
|
475
|
+
if os.path.isabs(project_path):
|
|
476
|
+
empathy_dir = os.path.join(project_path, ".empathy")
|
|
477
|
+
else:
|
|
478
|
+
empathy_dir = os.path.join(os.getcwd(), ".empathy")
|
|
479
|
+
|
|
480
|
+
os.makedirs(empathy_dir, exist_ok=True)
|
|
481
|
+
|
|
482
|
+
# Count issues by category
|
|
483
|
+
lint_errors = sum(1 for i in result.issues if i.get("category") == "lint")
|
|
484
|
+
type_errors = sum(1 for i in result.issues if i.get("category") == "types")
|
|
485
|
+
test_failures = sum(1 for i in result.issues if i.get("category") == "tests")
|
|
486
|
+
security_high = sum(
|
|
487
|
+
1
|
|
488
|
+
for i in result.issues
|
|
489
|
+
if i.get("category") == "security" and i.get("severity") == "high"
|
|
490
|
+
)
|
|
491
|
+
security_medium = sum(
|
|
492
|
+
1
|
|
493
|
+
for i in result.issues
|
|
494
|
+
if i.get("category") == "security" and i.get("severity") == "medium"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Extract test stats from checks_run
|
|
498
|
+
tests_info = result.checks_run.get("tests", {})
|
|
499
|
+
|
|
500
|
+
health_data = {
|
|
501
|
+
"score": result.health_score,
|
|
502
|
+
"lint": {"errors": lint_errors, "warnings": 0},
|
|
503
|
+
"types": {"errors": type_errors},
|
|
504
|
+
"security": {"high": security_high, "medium": security_medium, "low": 0},
|
|
505
|
+
"tests": {
|
|
506
|
+
"passed": tests_info.get("passed", 0) if tests_info.get("passed") else 0,
|
|
507
|
+
"failed": test_failures,
|
|
508
|
+
"total": tests_info.get("total", 0) if tests_info.get("total") else 0,
|
|
509
|
+
"coverage": tests_info.get("coverage", 0) if tests_info.get("coverage") else 0,
|
|
510
|
+
},
|
|
511
|
+
"tech_debt": {"total": 0, "todos": 0, "fixmes": 0, "hacks": 0},
|
|
512
|
+
"timestamp": datetime.now().isoformat(),
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
health_file = os.path.join(empathy_dir, "health.json")
|
|
516
|
+
with open(health_file, "w") as f:
|
|
517
|
+
json.dump(health_data, f, indent=2)
|
|
518
|
+
|
|
519
|
+
logger.info(f"Saved health data to {health_file}")
|
|
520
|
+
except OSError as e:
|
|
521
|
+
# File system errors (disk full, permission denied, etc.)
|
|
522
|
+
logger.warning(f"Failed to save health data (file system error): {e}")
|
|
523
|
+
except (TypeError, ValueError) as e:
|
|
524
|
+
# Cannot serialize health data - json.dump raises TypeError/ValueError
|
|
525
|
+
logger.error(f"Failed to save health data (serialization error): {e}")
|
|
526
|
+
except Exception as e: # noqa: BLE001
|
|
527
|
+
# INTENTIONAL: Saving health data should never crash a health check
|
|
528
|
+
# This is best-effort diagnostics output
|
|
529
|
+
logger.warning(f"Failed to save health data (unexpected error): {e}")
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def format_health_check_report(result: HealthCheckResult) -> str:
|
|
533
|
+
"""Format health check output as a human-readable report.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
result: The HealthCheckResult dataclass
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Formatted report string
|
|
540
|
+
|
|
541
|
+
"""
|
|
542
|
+
lines = []
|
|
543
|
+
|
|
544
|
+
# Header with health status
|
|
545
|
+
score = result.health_score
|
|
546
|
+
if score >= 90:
|
|
547
|
+
status_icon = "🟢"
|
|
548
|
+
status_text = "EXCELLENT"
|
|
549
|
+
elif score >= 80:
|
|
550
|
+
status_icon = "🟡"
|
|
551
|
+
status_text = "HEALTHY"
|
|
552
|
+
elif score >= 60:
|
|
553
|
+
status_icon = "🟠"
|
|
554
|
+
status_text = "NEEDS ATTENTION"
|
|
555
|
+
else:
|
|
556
|
+
status_icon = "🔴"
|
|
557
|
+
status_text = "UNHEALTHY"
|
|
558
|
+
|
|
559
|
+
lines.append("=" * 60)
|
|
560
|
+
lines.append("PROJECT HEALTH CHECK REPORT")
|
|
561
|
+
lines.append("=" * 60)
|
|
562
|
+
lines.append("")
|
|
563
|
+
lines.append(f"Health Score: {status_icon} {score:.0f}/100 ({status_text})")
|
|
564
|
+
lines.append(f"Status: {'✅ Healthy' if result.is_healthy else '⚠️ Issues Found'}")
|
|
565
|
+
lines.append("")
|
|
566
|
+
|
|
567
|
+
# Checks run summary
|
|
568
|
+
lines.append("-" * 60)
|
|
569
|
+
lines.append("CHECKS PERFORMED")
|
|
570
|
+
lines.append("-" * 60)
|
|
571
|
+
for check_name, check_result in result.checks_run.items():
|
|
572
|
+
passed = check_result.get("passed", False)
|
|
573
|
+
skipped = check_result.get("skipped", False)
|
|
574
|
+
if skipped:
|
|
575
|
+
icon = "⏭️"
|
|
576
|
+
status = "Skipped"
|
|
577
|
+
elif passed:
|
|
578
|
+
icon = "✅"
|
|
579
|
+
status = "Passed"
|
|
580
|
+
else:
|
|
581
|
+
icon = "❌"
|
|
582
|
+
status = "Failed"
|
|
583
|
+
lines.append(f" {icon} {check_name.capitalize()}: {status}")
|
|
584
|
+
lines.append("")
|
|
585
|
+
|
|
586
|
+
# Issue summary
|
|
587
|
+
if result.issues:
|
|
588
|
+
lines.append("-" * 60)
|
|
589
|
+
lines.append("ISSUES FOUND")
|
|
590
|
+
lines.append("-" * 60)
|
|
591
|
+
lines.append(f"Total: {len(result.issues)}")
|
|
592
|
+
lines.append(f" 🔴 Critical: {result.critical_count}")
|
|
593
|
+
lines.append(f" 🟠 High: {result.high_count}")
|
|
594
|
+
lines.append("")
|
|
595
|
+
|
|
596
|
+
# Group issues by category
|
|
597
|
+
by_category: dict[str, list] = {}
|
|
598
|
+
for issue in result.issues:
|
|
599
|
+
cat = issue.get("category", "other")
|
|
600
|
+
if cat not in by_category:
|
|
601
|
+
by_category[cat] = []
|
|
602
|
+
by_category[cat].append(issue)
|
|
603
|
+
|
|
604
|
+
for category, issues in by_category.items():
|
|
605
|
+
lines.append(f" {category.upper()} ({len(issues)} issues):")
|
|
606
|
+
for issue in issues[:5]: # Show top 5 per category
|
|
607
|
+
severity = issue.get("severity", "unknown").upper()
|
|
608
|
+
title = issue.get("title", "Unknown issue")
|
|
609
|
+
sev_icon = {"CRITICAL": "🔴", "HIGH": "🟠", "MEDIUM": "🟡", "LOW": "🟢"}.get(
|
|
610
|
+
severity,
|
|
611
|
+
"⚪",
|
|
612
|
+
)
|
|
613
|
+
lines.append(f" {sev_icon} [{severity}] {title}")
|
|
614
|
+
if len(issues) > 5:
|
|
615
|
+
lines.append(f" ... and {len(issues) - 5} more")
|
|
616
|
+
lines.append("")
|
|
617
|
+
|
|
618
|
+
# Fixes applied
|
|
619
|
+
if result.fixes:
|
|
620
|
+
lines.append("-" * 60)
|
|
621
|
+
lines.append("FIXES APPLIED")
|
|
622
|
+
lines.append("-" * 60)
|
|
623
|
+
lines.append(f"Total Fixes: {result.applied_fixes_count}")
|
|
624
|
+
for fix in result.fixes[:10]:
|
|
625
|
+
status = fix.get("status", "unknown")
|
|
626
|
+
title = fix.get("title", "Unknown fix")
|
|
627
|
+
status_icon = "✅" if status == "applied" else "⚠️"
|
|
628
|
+
lines.append(f" {status_icon} {title}")
|
|
629
|
+
lines.append("")
|
|
630
|
+
|
|
631
|
+
# Agents used
|
|
632
|
+
if result.agents_used:
|
|
633
|
+
lines.append("-" * 60)
|
|
634
|
+
lines.append("AGENTS USED")
|
|
635
|
+
lines.append("-" * 60)
|
|
636
|
+
for agent in result.agents_used:
|
|
637
|
+
lines.append(f" 🤖 {agent}")
|
|
638
|
+
lines.append("")
|
|
639
|
+
|
|
640
|
+
# Footer
|
|
641
|
+
lines.append("=" * 60)
|
|
642
|
+
duration_ms = result.duration_seconds * 1000
|
|
643
|
+
lines.append(f"Health check completed in {duration_ms:.0f}ms | Cost: ${result.cost:.4f}")
|
|
644
|
+
lines.append("=" * 60)
|
|
645
|
+
|
|
646
|
+
return "\n".join(lines)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def main():
|
|
650
|
+
"""CLI entry point for health check workflow."""
|
|
651
|
+
import asyncio
|
|
652
|
+
|
|
653
|
+
async def run():
|
|
654
|
+
workflow = HealthCheckWorkflow(auto_fix=False)
|
|
655
|
+
result = await workflow.execute(path=".")
|
|
656
|
+
|
|
657
|
+
print("\nHealth Check Results")
|
|
658
|
+
print("=" * 50)
|
|
659
|
+
print(f"Health Score: {result.health_score}/100")
|
|
660
|
+
print(f"Is Healthy: {result.is_healthy}")
|
|
661
|
+
print(f"Issues Found: {len(result.issues)}")
|
|
662
|
+
print(f" Critical: {result.critical_count}")
|
|
663
|
+
print(f" High: {result.high_count}")
|
|
664
|
+
|
|
665
|
+
if result.issues:
|
|
666
|
+
print("\nTop Issues:")
|
|
667
|
+
for issue in result.issues[:5]:
|
|
668
|
+
print(
|
|
669
|
+
f" - [{issue.get('severity', 'N/A').upper()}] {issue.get('title', 'Unknown')}",
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
print(f"\nChecks Run: {list(result.checks_run.keys())}")
|
|
673
|
+
print(f"Duration: {result.duration_seconds * 1000:.0f}ms")
|
|
674
|
+
|
|
675
|
+
asyncio.run(run())
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
if __name__ == "__main__":
|
|
679
|
+
main()
|