empathy-framework 4.6.6__py3-none-any.whl → 4.7.1__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.
- empathy_framework-4.7.1.dist-info/METADATA +690 -0
- empathy_framework-4.7.1.dist-info/RECORD +379 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/top_level.txt +1 -2
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
- empathy_llm_toolkit/agent_factory/__init__.py +6 -6
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
- empathy_llm_toolkit/agents_md/__init__.py +22 -0
- empathy_llm_toolkit/agents_md/loader.py +218 -0
- empathy_llm_toolkit/agents_md/parser.py +271 -0
- empathy_llm_toolkit/agents_md/registry.py +307 -0
- empathy_llm_toolkit/commands/__init__.py +51 -0
- empathy_llm_toolkit/commands/context.py +375 -0
- empathy_llm_toolkit/commands/loader.py +301 -0
- empathy_llm_toolkit/commands/models.py +231 -0
- empathy_llm_toolkit/commands/parser.py +371 -0
- empathy_llm_toolkit/commands/registry.py +429 -0
- empathy_llm_toolkit/config/__init__.py +8 -8
- empathy_llm_toolkit/config/unified.py +3 -7
- empathy_llm_toolkit/context/__init__.py +22 -0
- empathy_llm_toolkit/context/compaction.py +455 -0
- empathy_llm_toolkit/context/manager.py +434 -0
- empathy_llm_toolkit/hooks/__init__.py +24 -0
- empathy_llm_toolkit/hooks/config.py +306 -0
- empathy_llm_toolkit/hooks/executor.py +289 -0
- empathy_llm_toolkit/hooks/registry.py +302 -0
- empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
- empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
- empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
- empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
- empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
- empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
- empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
- empathy_llm_toolkit/learning/__init__.py +30 -0
- empathy_llm_toolkit/learning/evaluator.py +438 -0
- empathy_llm_toolkit/learning/extractor.py +514 -0
- empathy_llm_toolkit/learning/storage.py +560 -0
- empathy_llm_toolkit/providers.py +4 -11
- empathy_llm_toolkit/security/__init__.py +17 -17
- empathy_llm_toolkit/utils/tokens.py +2 -5
- empathy_os/__init__.py +202 -70
- empathy_os/cache_monitor.py +5 -3
- empathy_os/cli/__init__.py +11 -55
- empathy_os/cli/__main__.py +29 -15
- empathy_os/cli/commands/inspection.py +21 -12
- empathy_os/cli/commands/memory.py +4 -12
- empathy_os/cli/commands/profiling.py +198 -0
- empathy_os/cli/commands/utilities.py +27 -7
- empathy_os/cli.py +28 -57
- empathy_os/cli_unified.py +525 -1164
- empathy_os/cost_tracker.py +9 -3
- empathy_os/dashboard/server.py +200 -2
- empathy_os/hot_reload/__init__.py +7 -7
- empathy_os/hot_reload/config.py +6 -7
- empathy_os/hot_reload/integration.py +35 -35
- empathy_os/hot_reload/reloader.py +57 -57
- empathy_os/hot_reload/watcher.py +28 -28
- empathy_os/hot_reload/websocket.py +2 -2
- empathy_os/memory/__init__.py +11 -4
- empathy_os/memory/claude_memory.py +1 -1
- empathy_os/memory/cross_session.py +8 -12
- empathy_os/memory/edges.py +6 -6
- empathy_os/memory/file_session.py +770 -0
- empathy_os/memory/graph.py +30 -30
- empathy_os/memory/nodes.py +6 -6
- empathy_os/memory/short_term.py +15 -9
- empathy_os/memory/unified.py +606 -140
- empathy_os/meta_workflows/agent_creator.py +3 -9
- empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
- empathy_os/meta_workflows/form_engine.py +6 -18
- empathy_os/meta_workflows/intent_detector.py +64 -24
- empathy_os/meta_workflows/models.py +3 -1
- empathy_os/meta_workflows/pattern_learner.py +13 -31
- empathy_os/meta_workflows/plan_generator.py +55 -47
- empathy_os/meta_workflows/session_context.py +2 -3
- empathy_os/meta_workflows/workflow.py +20 -51
- empathy_os/models/cli.py +2 -2
- empathy_os/models/tasks.py +1 -2
- empathy_os/models/telemetry.py +4 -1
- empathy_os/models/token_estimator.py +3 -1
- empathy_os/monitoring/alerts.py +938 -9
- empathy_os/monitoring/alerts_cli.py +346 -183
- empathy_os/orchestration/execution_strategies.py +12 -29
- empathy_os/orchestration/pattern_learner.py +20 -26
- empathy_os/orchestration/real_tools.py +6 -15
- empathy_os/platform_utils.py +2 -1
- empathy_os/plugins/__init__.py +2 -2
- empathy_os/plugins/base.py +64 -64
- empathy_os/plugins/registry.py +32 -32
- empathy_os/project_index/index.py +49 -15
- empathy_os/project_index/models.py +1 -2
- empathy_os/project_index/reports.py +1 -1
- empathy_os/project_index/scanner.py +1 -0
- empathy_os/redis_memory.py +10 -7
- empathy_os/resilience/__init__.py +1 -1
- empathy_os/resilience/health.py +10 -10
- empathy_os/routing/__init__.py +7 -7
- empathy_os/routing/chain_executor.py +37 -37
- empathy_os/routing/classifier.py +36 -36
- empathy_os/routing/smart_router.py +40 -40
- empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
- empathy_os/scaffolding/__init__.py +8 -8
- empathy_os/scaffolding/__main__.py +1 -1
- empathy_os/scaffolding/cli.py +28 -28
- empathy_os/socratic/__init__.py +3 -19
- empathy_os/socratic/ab_testing.py +25 -36
- empathy_os/socratic/blueprint.py +38 -38
- empathy_os/socratic/cli.py +34 -20
- empathy_os/socratic/collaboration.py +30 -28
- empathy_os/socratic/domain_templates.py +9 -1
- empathy_os/socratic/embeddings.py +17 -13
- empathy_os/socratic/engine.py +135 -70
- empathy_os/socratic/explainer.py +70 -60
- empathy_os/socratic/feedback.py +24 -19
- empathy_os/socratic/forms.py +15 -10
- empathy_os/socratic/generator.py +51 -35
- empathy_os/socratic/llm_analyzer.py +25 -23
- empathy_os/socratic/mcp_server.py +99 -159
- empathy_os/socratic/session.py +19 -13
- empathy_os/socratic/storage.py +98 -67
- empathy_os/socratic/success.py +38 -27
- empathy_os/socratic/visual_editor.py +51 -39
- empathy_os/socratic/web_ui.py +99 -66
- empathy_os/telemetry/cli.py +3 -1
- empathy_os/telemetry/usage_tracker.py +1 -3
- empathy_os/test_generator/__init__.py +3 -3
- empathy_os/test_generator/cli.py +28 -28
- empathy_os/test_generator/generator.py +64 -66
- empathy_os/test_generator/risk_analyzer.py +11 -11
- empathy_os/vscode_bridge 2.py +173 -0
- empathy_os/vscode_bridge.py +173 -0
- empathy_os/workflows/__init__.py +212 -120
- empathy_os/workflows/batch_processing.py +8 -24
- empathy_os/workflows/bug_predict.py +1 -1
- empathy_os/workflows/code_review.py +20 -5
- empathy_os/workflows/code_review_pipeline.py +13 -8
- empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
- empathy_os/workflows/manage_documentation.py +1 -0
- empathy_os/workflows/orchestrated_health_check.py +6 -11
- empathy_os/workflows/orchestrated_release_prep.py +3 -3
- empathy_os/workflows/pr_review.py +18 -10
- empathy_os/workflows/progressive/README 2.md +454 -0
- empathy_os/workflows/progressive/__init__ 2.py +92 -0
- empathy_os/workflows/progressive/__init__.py +2 -12
- empathy_os/workflows/progressive/cli 2.py +242 -0
- empathy_os/workflows/progressive/cli.py +14 -37
- empathy_os/workflows/progressive/core 2.py +488 -0
- empathy_os/workflows/progressive/core.py +12 -12
- empathy_os/workflows/progressive/orchestrator 2.py +701 -0
- empathy_os/workflows/progressive/orchestrator.py +166 -144
- empathy_os/workflows/progressive/reports 2.py +528 -0
- empathy_os/workflows/progressive/reports.py +22 -31
- empathy_os/workflows/progressive/telemetry 2.py +280 -0
- empathy_os/workflows/progressive/telemetry.py +8 -14
- empathy_os/workflows/progressive/test_gen 2.py +514 -0
- empathy_os/workflows/progressive/test_gen.py +29 -48
- empathy_os/workflows/progressive/workflow 2.py +628 -0
- empathy_os/workflows/progressive/workflow.py +31 -70
- empathy_os/workflows/release_prep.py +21 -6
- empathy_os/workflows/release_prep_crew.py +1 -0
- empathy_os/workflows/secure_release.py +13 -6
- empathy_os/workflows/security_audit.py +8 -3
- empathy_os/workflows/test_coverage_boost_crew.py +3 -2
- empathy_os/workflows/test_maintenance_crew.py +1 -0
- empathy_os/workflows/test_runner.py +16 -12
- empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
- empathy_software_plugin/cli.py +0 -122
- patterns/README.md +119 -0
- patterns/__init__.py +95 -0
- patterns/behavior.py +298 -0
- patterns/code_review_memory.json +441 -0
- patterns/core.py +97 -0
- patterns/debugging.json +3763 -0
- patterns/empathy.py +268 -0
- patterns/health_check_memory.json +505 -0
- patterns/input.py +161 -0
- patterns/memory_graph.json +8 -0
- patterns/refactoring_memory.json +1113 -0
- patterns/registry.py +663 -0
- patterns/security_memory.json +8 -0
- patterns/structural.py +415 -0
- patterns/validation.py +194 -0
- coach_wizards/__init__.py +0 -45
- coach_wizards/accessibility_wizard.py +0 -91
- coach_wizards/api_wizard.py +0 -91
- coach_wizards/base_wizard.py +0 -209
- coach_wizards/cicd_wizard.py +0 -91
- coach_wizards/code_reviewer_README.md +0 -60
- coach_wizards/code_reviewer_wizard.py +0 -180
- coach_wizards/compliance_wizard.py +0 -91
- coach_wizards/database_wizard.py +0 -91
- coach_wizards/debugging_wizard.py +0 -91
- coach_wizards/documentation_wizard.py +0 -91
- coach_wizards/generate_wizards.py +0 -347
- coach_wizards/localization_wizard.py +0 -173
- coach_wizards/migration_wizard.py +0 -91
- coach_wizards/monitoring_wizard.py +0 -91
- coach_wizards/observability_wizard.py +0 -91
- coach_wizards/performance_wizard.py +0 -91
- coach_wizards/prompt_engineering_wizard.py +0 -661
- coach_wizards/refactoring_wizard.py +0 -91
- coach_wizards/scaling_wizard.py +0 -90
- coach_wizards/security_wizard.py +0 -92
- coach_wizards/testing_wizard.py +0 -91
- empathy_framework-4.6.6.dist-info/METADATA +0 -1597
- empathy_framework-4.6.6.dist-info/RECORD +0 -410
- empathy_llm_toolkit/wizards/__init__.py +0 -43
- empathy_llm_toolkit/wizards/base_wizard.py +0 -364
- empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
- empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
- empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
- empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
- empathy_os/wizard_factory_cli.py +0 -170
- empathy_software_plugin/wizards/__init__.py +0 -42
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
- empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
- empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
- empathy_software_plugin/wizards/base_wizard.py +0 -288
- empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
- empathy_software_plugin/wizards/code_review_wizard.py +0 -604
- empathy_software_plugin/wizards/debugging/__init__.py +0 -50
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
- empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
- empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
- empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
- empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
- empathy_software_plugin/wizards/debugging/verification.py +0 -369
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
- empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
- empathy_software_plugin/wizards/performance/__init__.py +0 -9
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
- empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
- empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
- empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
- empathy_software_plugin/wizards/security/__init__.py +0 -32
- empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
- empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
- empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
- empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
- empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
- empathy_software_plugin/wizards/testing/__init__.py +0 -27
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
- empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
- empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
- empathy_software_plugin/wizards/testing_wizard.py +0 -274
- wizards/__init__.py +0 -82
- wizards/admission_assessment_wizard.py +0 -644
- wizards/care_plan.py +0 -321
- wizards/clinical_assessment.py +0 -769
- wizards/discharge_planning.py +0 -77
- wizards/discharge_summary_wizard.py +0 -468
- wizards/dosage_calculation.py +0 -497
- wizards/incident_report_wizard.py +0 -454
- wizards/medication_reconciliation.py +0 -85
- wizards/nursing_assessment.py +0 -171
- wizards/patient_education.py +0 -654
- wizards/quality_improvement.py +0 -705
- wizards/sbar_report.py +0 -324
- wizards/sbar_wizard.py +0 -608
- wizards/shift_handoff_wizard.py +0 -535
- wizards/soap_note_wizard.py +0 -679
- wizards/treatment_plan.py +0 -15
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/WHEEL +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/entry_points.txt +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Testing Analysis Components
|
|
2
|
-
|
|
3
|
-
Supporting modules for Enhanced Testing Wizard.
|
|
4
|
-
|
|
5
|
-
Copyright 2025 Smart-AI-Memory
|
|
6
|
-
Licensed under Fair Source License 0.9
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from .coverage_analyzer import CoverageAnalyzer, CoverageReport, FileCoverage
|
|
10
|
-
from .quality_analyzer import TestFunction, TestQualityAnalyzer, TestQualityReport
|
|
11
|
-
from .test_suggester import CodeElement, TestPriority, TestSuggester, TestSuggestion
|
|
12
|
-
|
|
13
|
-
__all__ = [
|
|
14
|
-
"CodeElement",
|
|
15
|
-
# Coverage Analysis
|
|
16
|
-
"CoverageAnalyzer",
|
|
17
|
-
"CoverageReport",
|
|
18
|
-
"FileCoverage",
|
|
19
|
-
"TestFunction",
|
|
20
|
-
"TestPriority",
|
|
21
|
-
# Quality Analysis
|
|
22
|
-
"TestQualityAnalyzer",
|
|
23
|
-
"TestQualityReport",
|
|
24
|
-
# Test Suggestions
|
|
25
|
-
"TestSuggester",
|
|
26
|
-
"TestSuggestion",
|
|
27
|
-
]
|
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
"""Coverage Analyzer for Enhanced Testing Wizard
|
|
2
|
-
|
|
3
|
-
Parses coverage reports and provides intelligent analysis of test coverage gaps,
|
|
4
|
-
including branch coverage, critical path identification, and trend analysis.
|
|
5
|
-
|
|
6
|
-
Copyright 2025 Smart-AI-Memory
|
|
7
|
-
Licensed under Fair Source License 0.9
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import xml.etree.ElementTree as ET
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from enum import Enum
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from typing import Any
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class CoverageFormat(Enum):
|
|
19
|
-
"""Supported coverage report formats"""
|
|
20
|
-
|
|
21
|
-
XML = "xml"
|
|
22
|
-
JSON = "json"
|
|
23
|
-
HTML = "html"
|
|
24
|
-
LCOV = "lcov"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class FileCoverage:
|
|
29
|
-
"""Coverage statistics for a single file"""
|
|
30
|
-
|
|
31
|
-
file_path: str
|
|
32
|
-
lines_total: int
|
|
33
|
-
lines_covered: int
|
|
34
|
-
lines_missing: list[int]
|
|
35
|
-
branches_total: int
|
|
36
|
-
branches_covered: int
|
|
37
|
-
branches_missing: list[tuple[int, int]] # (line_num, branch_id)
|
|
38
|
-
percentage: float
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def lines_uncovered(self) -> int:
|
|
42
|
-
"""Number of uncovered lines"""
|
|
43
|
-
return self.lines_total - self.lines_covered
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def branch_percentage(self) -> float:
|
|
47
|
-
"""Branch coverage percentage"""
|
|
48
|
-
if self.branches_total == 0:
|
|
49
|
-
return 100.0
|
|
50
|
-
return (self.branches_covered / self.branches_total) * 100
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass
|
|
54
|
-
class CoverageReport:
|
|
55
|
-
"""Complete coverage analysis report"""
|
|
56
|
-
|
|
57
|
-
overall_percentage: float
|
|
58
|
-
lines_total: int
|
|
59
|
-
lines_covered: int
|
|
60
|
-
branches_total: int
|
|
61
|
-
branches_covered: int
|
|
62
|
-
files: dict[str, FileCoverage]
|
|
63
|
-
critical_gaps: list[str] # Files with <50% coverage
|
|
64
|
-
untested_files: list[str] # Files with 0% coverage
|
|
65
|
-
timestamp: str | None = None
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def files_total(self) -> int:
|
|
69
|
-
"""Total number of files analyzed"""
|
|
70
|
-
return len(self.files)
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def files_well_covered(self) -> int:
|
|
74
|
-
"""Number of files with ≥80% coverage"""
|
|
75
|
-
return sum(1 for f in self.files.values() if f.percentage >= 80.0)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class CoverageAnalyzer:
|
|
79
|
-
"""Analyzes test coverage reports to identify gaps and provide recommendations.
|
|
80
|
-
|
|
81
|
-
Supports multiple formats:
|
|
82
|
-
- Coverage.py XML (pytest --cov)
|
|
83
|
-
- Coverage.py JSON
|
|
84
|
-
- LCOV format
|
|
85
|
-
- HTML reports
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
def __init__(self):
|
|
89
|
-
self.critical_threshold = 50.0 # Files below this are critical
|
|
90
|
-
self.target_threshold = 80.0 # Target coverage percentage
|
|
91
|
-
|
|
92
|
-
def parse_coverage_xml(self, xml_path: Path) -> CoverageReport:
|
|
93
|
-
"""Parse coverage.xml format (Coverage.py / pytest-cov output)
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
xml_path: Path to coverage.xml file
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
CoverageReport with full analysis
|
|
100
|
-
|
|
101
|
-
Raises:
|
|
102
|
-
FileNotFoundError: If XML file doesn't exist
|
|
103
|
-
ValueError: If XML is malformed
|
|
104
|
-
|
|
105
|
-
"""
|
|
106
|
-
if not xml_path.exists():
|
|
107
|
-
raise FileNotFoundError(f"Coverage XML not found: {xml_path}")
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
# Note: Parsing trusted coverage.xml generated by pytest-cov, not untrusted user input
|
|
111
|
-
tree = ET.parse(xml_path) # nosec B314
|
|
112
|
-
root = tree.getroot()
|
|
113
|
-
except ET.ParseError as e:
|
|
114
|
-
raise ValueError(f"Malformed coverage XML: {e}") from e
|
|
115
|
-
|
|
116
|
-
# Extract overall statistics
|
|
117
|
-
overall = root.attrib
|
|
118
|
-
lines_total = int(overall.get("lines-valid", 0))
|
|
119
|
-
lines_covered = int(overall.get("lines-covered", 0))
|
|
120
|
-
branches_total = int(overall.get("branches-valid", 0))
|
|
121
|
-
branches_covered = int(overall.get("branches-covered", 0))
|
|
122
|
-
|
|
123
|
-
# Calculate overall percentage
|
|
124
|
-
if lines_total > 0:
|
|
125
|
-
overall_pct = (lines_covered / lines_total) * 100
|
|
126
|
-
else:
|
|
127
|
-
overall_pct = 0.0
|
|
128
|
-
|
|
129
|
-
# Parse individual files
|
|
130
|
-
files: dict[str, FileCoverage] = {}
|
|
131
|
-
critical_gaps: list[str] = []
|
|
132
|
-
untested_files: list[str] = []
|
|
133
|
-
|
|
134
|
-
for package in root.findall(".//package"):
|
|
135
|
-
for cls in package.findall(".//class"):
|
|
136
|
-
filename = cls.attrib.get("filename", "")
|
|
137
|
-
if not filename:
|
|
138
|
-
continue
|
|
139
|
-
|
|
140
|
-
# Get line statistics
|
|
141
|
-
lines = cls.findall(".//line")
|
|
142
|
-
lines_total_file = len(lines)
|
|
143
|
-
lines_covered_file = sum(1 for line in lines if line.attrib.get("hits", "0") != "0")
|
|
144
|
-
lines_missing = [
|
|
145
|
-
int(line.attrib.get("number", 0))
|
|
146
|
-
for line in lines
|
|
147
|
-
if line.attrib.get("hits", "0") == "0"
|
|
148
|
-
]
|
|
149
|
-
|
|
150
|
-
# Get branch statistics
|
|
151
|
-
branches_file = [line for line in lines if line.attrib.get("branch") == "true"]
|
|
152
|
-
branches_total_file = len(branches_file) * 2 # Each branch point has 2 paths
|
|
153
|
-
branches_covered_file = 0
|
|
154
|
-
for line in branches_file:
|
|
155
|
-
if "condition-coverage" in line.attrib:
|
|
156
|
-
# Format: "50% (1/2)" or "0% (0/2)"
|
|
157
|
-
cov_str = line.attrib.get("condition-coverage", "0% (0/2)")
|
|
158
|
-
# Extract the fraction part: (1/2)
|
|
159
|
-
if "(" in cov_str:
|
|
160
|
-
fraction = cov_str.split("(")[1].split(")")[0] # "1/2"
|
|
161
|
-
covered = int(fraction.split("/")[0]) # 1
|
|
162
|
-
branches_covered_file += covered
|
|
163
|
-
branches_missing = [
|
|
164
|
-
(int(line.attrib.get("number", 0)), i)
|
|
165
|
-
for line in branches_file
|
|
166
|
-
for i in range(2) # Each branch has 2 paths
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
# Calculate file percentage
|
|
170
|
-
if lines_total_file > 0:
|
|
171
|
-
file_pct = (lines_covered_file / lines_total_file) * 100
|
|
172
|
-
else:
|
|
173
|
-
file_pct = 0.0
|
|
174
|
-
|
|
175
|
-
file_cov = FileCoverage(
|
|
176
|
-
file_path=filename,
|
|
177
|
-
lines_total=lines_total_file,
|
|
178
|
-
lines_covered=lines_covered_file,
|
|
179
|
-
lines_missing=lines_missing,
|
|
180
|
-
branches_total=branches_total_file,
|
|
181
|
-
branches_covered=branches_covered_file,
|
|
182
|
-
branches_missing=branches_missing,
|
|
183
|
-
percentage=file_pct,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
files[filename] = file_cov
|
|
187
|
-
|
|
188
|
-
# Track critical gaps
|
|
189
|
-
if file_pct < self.critical_threshold:
|
|
190
|
-
critical_gaps.append(filename)
|
|
191
|
-
if file_pct == 0.0 and lines_total_file > 0:
|
|
192
|
-
untested_files.append(filename)
|
|
193
|
-
|
|
194
|
-
return CoverageReport(
|
|
195
|
-
overall_percentage=overall_pct,
|
|
196
|
-
lines_total=lines_total,
|
|
197
|
-
lines_covered=lines_covered,
|
|
198
|
-
branches_total=branches_total,
|
|
199
|
-
branches_covered=branches_covered,
|
|
200
|
-
files=files,
|
|
201
|
-
critical_gaps=critical_gaps,
|
|
202
|
-
untested_files=untested_files,
|
|
203
|
-
timestamp=overall.get("timestamp"),
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
def parse_coverage_json(self, json_path: Path) -> CoverageReport:
|
|
207
|
-
"""Parse coverage.json format (Coverage.py JSON output)
|
|
208
|
-
|
|
209
|
-
Args:
|
|
210
|
-
json_path: Path to coverage.json file
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
CoverageReport with full analysis
|
|
214
|
-
|
|
215
|
-
"""
|
|
216
|
-
if not json_path.exists():
|
|
217
|
-
raise FileNotFoundError(f"Coverage JSON not found: {json_path}")
|
|
218
|
-
|
|
219
|
-
with open(json_path) as f:
|
|
220
|
-
data = json.load(f)
|
|
221
|
-
|
|
222
|
-
# Extract overall totals
|
|
223
|
-
totals = data.get("totals", {})
|
|
224
|
-
lines_total = totals.get("num_statements", 0)
|
|
225
|
-
lines_covered = totals.get("covered_lines", 0)
|
|
226
|
-
branches_total = totals.get("num_branches", 0)
|
|
227
|
-
branches_covered = totals.get("covered_branches", 0)
|
|
228
|
-
|
|
229
|
-
if lines_total > 0:
|
|
230
|
-
overall_pct = (lines_covered / lines_total) * 100
|
|
231
|
-
else:
|
|
232
|
-
overall_pct = 0.0
|
|
233
|
-
|
|
234
|
-
# Parse individual files
|
|
235
|
-
files: dict[str, FileCoverage] = {}
|
|
236
|
-
critical_gaps: list[str] = []
|
|
237
|
-
untested_files: list[str] = []
|
|
238
|
-
|
|
239
|
-
for filename, file_data in data.get("files", {}).items():
|
|
240
|
-
summary = file_data.get("summary", {})
|
|
241
|
-
|
|
242
|
-
lines_total_file = summary.get("num_statements", 0)
|
|
243
|
-
lines_covered_file = summary.get("covered_lines", 0)
|
|
244
|
-
branches_total_file = summary.get("num_branches", 0)
|
|
245
|
-
branches_covered_file = summary.get("covered_branches", 0)
|
|
246
|
-
|
|
247
|
-
# Get missing lines
|
|
248
|
-
missing_lines = file_data.get("missing_lines", [])
|
|
249
|
-
|
|
250
|
-
# Get missing branches
|
|
251
|
-
missing_branches = [
|
|
252
|
-
(line, branch)
|
|
253
|
-
for line, branches in file_data.get("missing_branches", {}).items()
|
|
254
|
-
for branch in branches
|
|
255
|
-
]
|
|
256
|
-
|
|
257
|
-
if lines_total_file > 0:
|
|
258
|
-
file_pct = (lines_covered_file / lines_total_file) * 100
|
|
259
|
-
else:
|
|
260
|
-
file_pct = 0.0
|
|
261
|
-
|
|
262
|
-
file_cov = FileCoverage(
|
|
263
|
-
file_path=filename,
|
|
264
|
-
lines_total=lines_total_file,
|
|
265
|
-
lines_covered=lines_covered_file,
|
|
266
|
-
lines_missing=missing_lines,
|
|
267
|
-
branches_total=branches_total_file,
|
|
268
|
-
branches_covered=branches_covered_file,
|
|
269
|
-
branches_missing=missing_branches,
|
|
270
|
-
percentage=file_pct,
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
files[filename] = file_cov
|
|
274
|
-
|
|
275
|
-
if file_pct < self.critical_threshold:
|
|
276
|
-
critical_gaps.append(filename)
|
|
277
|
-
if file_pct == 0.0 and lines_total_file > 0:
|
|
278
|
-
untested_files.append(filename)
|
|
279
|
-
|
|
280
|
-
return CoverageReport(
|
|
281
|
-
overall_percentage=overall_pct,
|
|
282
|
-
lines_total=lines_total,
|
|
283
|
-
lines_covered=lines_covered,
|
|
284
|
-
branches_total=branches_total,
|
|
285
|
-
branches_covered=branches_covered,
|
|
286
|
-
files=files,
|
|
287
|
-
critical_gaps=critical_gaps,
|
|
288
|
-
untested_files=untested_files,
|
|
289
|
-
timestamp=data.get("meta", {}).get("timestamp"),
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
def identify_critical_gaps(self, report: CoverageReport) -> list[str]:
|
|
293
|
-
"""Identify files that critically need testing (below threshold)
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
report: Coverage report to analyze
|
|
297
|
-
|
|
298
|
-
Returns:
|
|
299
|
-
List of file paths sorted by priority (worst coverage first)
|
|
300
|
-
|
|
301
|
-
"""
|
|
302
|
-
critical = [
|
|
303
|
-
(file_path, file_cov.percentage)
|
|
304
|
-
for file_path, file_cov in report.files.items()
|
|
305
|
-
if file_cov.percentage < self.critical_threshold
|
|
306
|
-
]
|
|
307
|
-
|
|
308
|
-
# Sort by percentage (lowest first)
|
|
309
|
-
critical.sort(key=lambda x: x[1])
|
|
310
|
-
|
|
311
|
-
return [file_path for file_path, _ in critical]
|
|
312
|
-
|
|
313
|
-
def suggest_priority_files(
|
|
314
|
-
self,
|
|
315
|
-
report: CoverageReport,
|
|
316
|
-
top_n: int = 10,
|
|
317
|
-
) -> list[dict[str, Any]]:
|
|
318
|
-
"""Suggest which files to test next for maximum impact
|
|
319
|
-
|
|
320
|
-
Prioritizes based on:
|
|
321
|
-
1. Current coverage (lower is higher priority)
|
|
322
|
-
2. File size (larger files have more impact)
|
|
323
|
-
3. Missing lines count (more gaps = more opportunity)
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
report: Coverage report to analyze
|
|
327
|
-
top_n: Number of suggestions to return
|
|
328
|
-
|
|
329
|
-
Returns:
|
|
330
|
-
List of suggestions with reasoning
|
|
331
|
-
|
|
332
|
-
"""
|
|
333
|
-
suggestions = []
|
|
334
|
-
|
|
335
|
-
for file_path, file_cov in report.files.items():
|
|
336
|
-
# Skip well-covered files
|
|
337
|
-
if file_cov.percentage >= self.target_threshold:
|
|
338
|
-
continue
|
|
339
|
-
|
|
340
|
-
# Calculate impact score
|
|
341
|
-
coverage_gap = self.target_threshold - file_cov.percentage
|
|
342
|
-
size_factor = file_cov.lines_total / 100 # Normalize
|
|
343
|
-
missing_factor = file_cov.lines_uncovered / 10 # Normalize
|
|
344
|
-
|
|
345
|
-
impact_score = (coverage_gap * 0.5) + (size_factor * 0.3) + (missing_factor * 0.2)
|
|
346
|
-
|
|
347
|
-
suggestions.append(
|
|
348
|
-
{
|
|
349
|
-
"file": file_path,
|
|
350
|
-
"current_coverage": file_cov.percentage,
|
|
351
|
-
"lines_missing": file_cov.lines_uncovered,
|
|
352
|
-
"impact_score": impact_score,
|
|
353
|
-
"reason": self._generate_suggestion_reason(file_cov),
|
|
354
|
-
},
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
# Sort by impact score (highest first)
|
|
358
|
-
suggestions.sort(key=lambda x: x["impact_score"], reverse=True)
|
|
359
|
-
|
|
360
|
-
return suggestions[:top_n]
|
|
361
|
-
|
|
362
|
-
def _generate_suggestion_reason(self, file_cov: FileCoverage) -> str:
|
|
363
|
-
"""Generate human-readable reason for testing suggestion"""
|
|
364
|
-
if file_cov.percentage == 0.0:
|
|
365
|
-
return "No tests exist - critical gap"
|
|
366
|
-
if file_cov.percentage < 30:
|
|
367
|
-
return "Very low coverage - high priority"
|
|
368
|
-
if file_cov.percentage < 50:
|
|
369
|
-
return "Below critical threshold"
|
|
370
|
-
if file_cov.percentage < 70:
|
|
371
|
-
return "Moderate gap - good opportunity for improvement"
|
|
372
|
-
return "Close to target - finish with targeted tests"
|
|
373
|
-
|
|
374
|
-
def calculate_coverage_trend(
|
|
375
|
-
self,
|
|
376
|
-
historical_reports: list[tuple[str, CoverageReport]],
|
|
377
|
-
) -> dict[str, float]:
|
|
378
|
-
"""Calculate coverage trends over time
|
|
379
|
-
|
|
380
|
-
Args:
|
|
381
|
-
historical_reports: List of (timestamp, report) tuples in chronological order
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
Dict mapping file paths to trend percentage (positive = improving)
|
|
385
|
-
|
|
386
|
-
"""
|
|
387
|
-
if len(historical_reports) < 2:
|
|
388
|
-
return {}
|
|
389
|
-
|
|
390
|
-
trends = {}
|
|
391
|
-
|
|
392
|
-
# Get oldest and newest reports
|
|
393
|
-
oldest_date, oldest_report = historical_reports[0]
|
|
394
|
-
newest_date, newest_report = historical_reports[-1]
|
|
395
|
-
|
|
396
|
-
# Calculate trend for each file
|
|
397
|
-
all_files = set(oldest_report.files.keys()) | set(newest_report.files.keys())
|
|
398
|
-
|
|
399
|
-
for file_path in all_files:
|
|
400
|
-
old_cov = oldest_report.files.get(file_path)
|
|
401
|
-
new_cov = newest_report.files.get(file_path)
|
|
402
|
-
|
|
403
|
-
if old_cov and new_cov:
|
|
404
|
-
trend = new_cov.percentage - old_cov.percentage
|
|
405
|
-
trends[file_path] = trend
|
|
406
|
-
elif new_cov and not old_cov:
|
|
407
|
-
# New file added
|
|
408
|
-
trends[file_path] = new_cov.percentage
|
|
409
|
-
elif old_cov and not new_cov:
|
|
410
|
-
# File removed
|
|
411
|
-
trends[file_path] = -old_cov.percentage
|
|
412
|
-
|
|
413
|
-
return trends
|
|
414
|
-
|
|
415
|
-
def generate_summary(self, report: CoverageReport) -> str:
|
|
416
|
-
"""Generate human-readable coverage summary
|
|
417
|
-
|
|
418
|
-
Args:
|
|
419
|
-
report: Coverage report to summarize
|
|
420
|
-
|
|
421
|
-
Returns:
|
|
422
|
-
Formatted summary string
|
|
423
|
-
|
|
424
|
-
"""
|
|
425
|
-
summary = []
|
|
426
|
-
summary.append("=" * 60)
|
|
427
|
-
summary.append("COVERAGE ANALYSIS SUMMARY")
|
|
428
|
-
summary.append("=" * 60)
|
|
429
|
-
summary.append(f"Overall Coverage: {report.overall_percentage:.2f}%")
|
|
430
|
-
summary.append(f"Lines: {report.lines_covered}/{report.lines_total}")
|
|
431
|
-
summary.append(f"Branches: {report.branches_covered}/{report.branches_total}")
|
|
432
|
-
summary.append(f"Files: {report.files_total} total")
|
|
433
|
-
summary.append(f" - Well covered (≥80%): {report.files_well_covered}")
|
|
434
|
-
summary.append(f" - Critical gaps (<50%): {len(report.critical_gaps)}")
|
|
435
|
-
summary.append(f" - Untested: {len(report.untested_files)}")
|
|
436
|
-
summary.append("")
|
|
437
|
-
|
|
438
|
-
if report.untested_files:
|
|
439
|
-
summary.append("⚠️ UNTESTED FILES (0% coverage):")
|
|
440
|
-
for file_path in report.untested_files[:5]:
|
|
441
|
-
summary.append(f" - {file_path}")
|
|
442
|
-
if len(report.untested_files) > 5:
|
|
443
|
-
summary.append(f" ... and {len(report.untested_files) - 5} more")
|
|
444
|
-
summary.append("")
|
|
445
|
-
|
|
446
|
-
if report.critical_gaps:
|
|
447
|
-
summary.append("🔴 CRITICAL GAPS (<50% coverage):")
|
|
448
|
-
critical_sorted = sorted(
|
|
449
|
-
[(f, report.files[f].percentage) for f in report.critical_gaps],
|
|
450
|
-
key=lambda x: x[1],
|
|
451
|
-
)
|
|
452
|
-
for file_path, pct in critical_sorted[:5]:
|
|
453
|
-
summary.append(f" - {file_path}: {pct:.1f}%")
|
|
454
|
-
if len(report.critical_gaps) > 5:
|
|
455
|
-
summary.append(f" ... and {len(report.critical_gaps) - 5} more")
|
|
456
|
-
|
|
457
|
-
summary.append("=" * 60)
|
|
458
|
-
|
|
459
|
-
return "\n".join(summary)
|