codeshift 0.5.0__tar.gz → 0.7.3__tar.gz

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.
Files changed (113) hide show
  1. {codeshift-0.5.0 → codeshift-0.7.3}/PKG-INFO +21 -14
  2. {codeshift-0.5.0 → codeshift-0.7.3}/README.md +20 -13
  3. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/__init__.py +1 -1
  4. codeshift-0.7.3/codeshift/cli/commands/health.py +244 -0
  5. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/main.py +2 -0
  6. codeshift-0.7.3/codeshift/health/__init__.py +50 -0
  7. codeshift-0.7.3/codeshift/health/calculator.py +217 -0
  8. codeshift-0.7.3/codeshift/health/metrics/__init__.py +63 -0
  9. codeshift-0.7.3/codeshift/health/metrics/documentation.py +209 -0
  10. codeshift-0.7.3/codeshift/health/metrics/freshness.py +180 -0
  11. codeshift-0.7.3/codeshift/health/metrics/migration_readiness.py +142 -0
  12. codeshift-0.7.3/codeshift/health/metrics/security.py +225 -0
  13. codeshift-0.7.3/codeshift/health/metrics/test_coverage.py +191 -0
  14. codeshift-0.7.3/codeshift/health/models.py +284 -0
  15. codeshift-0.7.3/codeshift/health/report.py +310 -0
  16. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/PKG-INFO +21 -14
  17. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/SOURCES.txt +12 -0
  18. {codeshift-0.5.0 → codeshift-0.7.3}/pyproject.toml +1 -1
  19. codeshift-0.7.3/tests/test_health.py +600 -0
  20. {codeshift-0.5.0 → codeshift-0.7.3}/LICENSE +0 -0
  21. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/analyzer/__init__.py +0 -0
  22. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/analyzer/risk_assessor.py +0 -0
  23. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/__init__.py +0 -0
  24. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/__init__.py +0 -0
  25. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/apply.py +0 -0
  26. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/auth.py +0 -0
  27. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/diff.py +0 -0
  28. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/scan.py +0 -0
  29. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/upgrade.py +0 -0
  30. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/commands/upgrade_all.py +0 -0
  31. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/package_manager.py +0 -0
  32. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/cli/quota.py +0 -0
  33. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/__init__.py +0 -0
  34. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/cache.py +0 -0
  35. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/generator.py +0 -0
  36. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/models.py +0 -0
  37. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/parser.py +0 -0
  38. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge/sources.py +0 -0
  39. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/__init__.py +0 -0
  40. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/aiohttp.yaml +0 -0
  41. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/attrs.yaml +0 -0
  42. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/celery.yaml +0 -0
  43. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/click.yaml +0 -0
  44. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/django.yaml +0 -0
  45. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/fastapi.yaml +0 -0
  46. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/flask.yaml +0 -0
  47. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/httpx.yaml +0 -0
  48. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/marshmallow.yaml +0 -0
  49. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/numpy.yaml +0 -0
  50. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/pandas.yaml +0 -0
  51. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/pydantic.yaml +0 -0
  52. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/pytest.yaml +0 -0
  53. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/requests.yaml +0 -0
  54. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/libraries/sqlalchemy.yaml +0 -0
  55. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/loader.py +0 -0
  56. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/knowledge_base/models.py +0 -0
  57. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/__init__.py +0 -0
  58. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/ast_transforms.py +0 -0
  59. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/engine.py +0 -0
  60. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/llm_migrator.py +0 -0
  61. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/__init__.py +0 -0
  62. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/aiohttp_transformer.py +0 -0
  63. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/attrs_transformer.py +0 -0
  64. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/celery_transformer.py +0 -0
  65. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/click_transformer.py +0 -0
  66. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/django_transformer.py +0 -0
  67. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/fastapi_transformer.py +0 -0
  68. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/flask_transformer.py +0 -0
  69. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/httpx_transformer.py +0 -0
  70. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/marshmallow_transformer.py +0 -0
  71. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/numpy_transformer.py +0 -0
  72. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/pandas_transformer.py +0 -0
  73. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/pydantic_v1_to_v2.py +0 -0
  74. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/pytest_transformer.py +0 -0
  75. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/requests_transformer.py +0 -0
  76. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/migrator/transforms/sqlalchemy_transformer.py +0 -0
  77. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/scanner/__init__.py +0 -0
  78. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/scanner/code_scanner.py +0 -0
  79. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/scanner/dependency_parser.py +0 -0
  80. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/__init__.py +0 -0
  81. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/api_client.py +0 -0
  82. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/cache.py +0 -0
  83. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/config.py +0 -0
  84. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/credential_store.py +0 -0
  85. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/utils/llm_client.py +0 -0
  86. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/validator/__init__.py +0 -0
  87. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/validator/syntax_checker.py +0 -0
  88. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift/validator/test_runner.py +0 -0
  89. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/dependency_links.txt +0 -0
  90. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/entry_points.txt +0 -0
  91. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/requires.txt +0 -0
  92. {codeshift-0.5.0 → codeshift-0.7.3}/codeshift.egg-info/top_level.txt +0 -0
  93. {codeshift-0.5.0 → codeshift-0.7.3}/setup.cfg +0 -0
  94. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_aiohttp_transforms.py +0 -0
  95. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_attrs_transforms.py +0 -0
  96. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_celery_transforms.py +0 -0
  97. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_click_transforms.py +0 -0
  98. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_code_scanner.py +0 -0
  99. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_django_transforms.py +0 -0
  100. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_fastapi_transforms.py +0 -0
  101. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_flask_transforms.py +0 -0
  102. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_httpx_transforms.py +0 -0
  103. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_knowledge_base.py +0 -0
  104. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_marshmallow_transforms.py +0 -0
  105. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_numpy_transforms.py +0 -0
  106. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_pandas_transforms.py +0 -0
  107. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_pydantic_transforms.py +0 -0
  108. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_pydantic_type_inference.py +0 -0
  109. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_pytest_transforms.py +0 -0
  110. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_requests_transforms.py +0 -0
  111. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_risk_assessor.py +0 -0
  112. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_sqlalchemy_transforms.py +0 -0
  113. {codeshift-0.5.0 → codeshift-0.7.3}/tests/test_syntax_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeshift
3
- Version: 0.5.0
3
+ Version: 0.7.3
4
4
  Summary: AI-powered CLI tool that migrates Python code to handle breaking dependency changes
5
5
  Author: Ragab Technologies
6
6
  License: MIT
@@ -44,7 +44,10 @@ Dynamic: license-file
44
44
 
45
45
  # Codeshift
46
46
 
47
- [![PyPI version](https://badge.fury.io/py/codeshift.svg)](https://pypi.org/project/codeshift/)
47
+ [![CI](https://github.com/Ragab-Technologies/codeshift/actions/workflows/ci.yml/badge.svg)](https://github.com/Ragab-Technologies/codeshift/actions/workflows/ci.yml)
48
+ [![codecov](https://codecov.io/gh/Ragab-Technologies/codeshift/branch/main/graph/badge.svg)](https://codecov.io/gh/Ragab-Technologies/codeshift)
49
+ [![PyPI version](https://img.shields.io/pypi/v/codeshift)](https://pypi.org/project/codeshift/)
50
+ [![Downloads](https://static.pepy.tech/badge/codeshift/month)](https://pepy.tech/project/codeshift)
48
51
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
49
52
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
50
53
 
@@ -118,25 +121,29 @@ codeshift --help
118
121
  ## Quick Start
119
122
 
120
123
  ```bash
121
- # 1. Scan your project for outdated dependencies
122
- codeshift scan
123
-
124
- # 2. Upgrade a specific library
124
+ pip install codeshift
125
125
  codeshift upgrade pydantic --target 2.5.0
126
+ codeshift diff && codeshift apply
127
+ ```
126
128
 
127
- # 3. Review the proposed changes
128
- codeshift diff
129
+ That's it! Codeshift scans your code, transforms it, and shows exactly what changed.
129
130
 
130
- # 4. Apply the changes
131
- codeshift apply
131
+ ### Before / After
132
132
 
133
- # 5. Run your tests to verify
134
- pytest
135
- ```
133
+ | Pydantic v1 | Pydantic v2 |
134
+ |-------------|-------------|
135
+ | `class Config:` | `model_config = ConfigDict()` |
136
+ | `@validator` | `@field_validator` |
137
+ | `.dict()` | `.model_dump()` |
138
+ | `parse_obj()` | `model_validate()` |
136
139
 
137
- Or upgrade everything at once:
140
+ ### More Options
138
141
 
139
142
  ```bash
143
+ # Scan your project for all outdated dependencies
144
+ codeshift scan
145
+
146
+ # Or upgrade everything at once
140
147
  codeshift upgrade-all
141
148
  ```
142
149
 
@@ -1,6 +1,9 @@
1
1
  # Codeshift
2
2
 
3
- [![PyPI version](https://badge.fury.io/py/codeshift.svg)](https://pypi.org/project/codeshift/)
3
+ [![CI](https://github.com/Ragab-Technologies/codeshift/actions/workflows/ci.yml/badge.svg)](https://github.com/Ragab-Technologies/codeshift/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/Ragab-Technologies/codeshift/branch/main/graph/badge.svg)](https://codecov.io/gh/Ragab-Technologies/codeshift)
5
+ [![PyPI version](https://img.shields.io/pypi/v/codeshift)](https://pypi.org/project/codeshift/)
6
+ [![Downloads](https://static.pepy.tech/badge/codeshift/month)](https://pepy.tech/project/codeshift)
4
7
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
9
 
@@ -74,25 +77,29 @@ codeshift --help
74
77
  ## Quick Start
75
78
 
76
79
  ```bash
77
- # 1. Scan your project for outdated dependencies
78
- codeshift scan
79
-
80
- # 2. Upgrade a specific library
80
+ pip install codeshift
81
81
  codeshift upgrade pydantic --target 2.5.0
82
+ codeshift diff && codeshift apply
83
+ ```
82
84
 
83
- # 3. Review the proposed changes
84
- codeshift diff
85
+ That's it! Codeshift scans your code, transforms it, and shows exactly what changed.
85
86
 
86
- # 4. Apply the changes
87
- codeshift apply
87
+ ### Before / After
88
88
 
89
- # 5. Run your tests to verify
90
- pytest
91
- ```
89
+ | Pydantic v1 | Pydantic v2 |
90
+ |-------------|-------------|
91
+ | `class Config:` | `model_config = ConfigDict()` |
92
+ | `@validator` | `@field_validator` |
93
+ | `.dict()` | `.model_dump()` |
94
+ | `parse_obj()` | `model_validate()` |
92
95
 
93
- Or upgrade everything at once:
96
+ ### More Options
94
97
 
95
98
  ```bash
99
+ # Scan your project for all outdated dependencies
100
+ codeshift scan
101
+
102
+ # Or upgrade everything at once
96
103
  codeshift upgrade-all
97
104
  ```
98
105
 
@@ -4,5 +4,5 @@ Codeshift - AI-powered CLI tool for migrating Python code to handle breaking dep
4
4
  Don't just flag the update. Fix the break.
5
5
  """
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.7.3"
8
8
  __author__ = "Codeshift Team"
@@ -0,0 +1,244 @@
1
+ """CLI command for codebase health scoring."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import click
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.table import Table
10
+
11
+ from codeshift.health.calculator import HealthCalculator
12
+ from codeshift.health.models import HealthGrade, HealthScore, MetricCategory
13
+ from codeshift.health.report import generate_json_report, save_html_report, save_json_report
14
+
15
+ console = Console()
16
+
17
+
18
+ @click.command()
19
+ @click.option(
20
+ "--path",
21
+ "-p",
22
+ type=click.Path(exists=True, file_okay=False, dir_okay=True),
23
+ default=".",
24
+ help="Path to the project (default: current directory)",
25
+ )
26
+ @click.option(
27
+ "--report",
28
+ "-r",
29
+ type=click.Choice(["json", "html"]),
30
+ help="Generate a detailed report in the specified format",
31
+ )
32
+ @click.option(
33
+ "--output",
34
+ "-o",
35
+ type=click.Path(),
36
+ help="Output file path for the report (default: health_report.<format>)",
37
+ )
38
+ @click.option(
39
+ "--ci",
40
+ is_flag=True,
41
+ help="CI mode: exit with non-zero status if score is below threshold",
42
+ )
43
+ @click.option(
44
+ "--threshold",
45
+ type=int,
46
+ default=70,
47
+ help="Minimum score for CI mode (default: 70)",
48
+ )
49
+ @click.option(
50
+ "--verbose",
51
+ "-v",
52
+ is_flag=True,
53
+ help="Show detailed output including all dependencies",
54
+ )
55
+ def health(
56
+ path: str,
57
+ report: str | None,
58
+ output: str | None,
59
+ ci: bool,
60
+ threshold: int,
61
+ verbose: bool,
62
+ ) -> None:
63
+ """Analyze codebase health and generate a score.
64
+
65
+ Evaluates your project across five dimensions:
66
+ - Dependency Freshness (30%): How up-to-date are your dependencies?
67
+ - Security (25%): Known vulnerabilities in dependencies
68
+ - Migration Readiness (20%): Tier 1/2 support coverage
69
+ - Test Coverage (15%): Percentage of code covered by tests
70
+ - Documentation (10%): Type hints and docstrings
71
+
72
+ \b
73
+ Examples:
74
+ codeshift health # Show health summary
75
+ codeshift health --report html # Generate HTML report
76
+ codeshift health --report json -o report.json
77
+ codeshift health --ci --threshold 70 # CI mode
78
+
79
+ """
80
+ project_path = Path(path).resolve()
81
+
82
+ with console.status("[bold blue]Analyzing codebase health..."):
83
+ calculator = HealthCalculator()
84
+ score = calculator.calculate(project_path)
85
+
86
+ # Handle report generation
87
+ if report:
88
+ output_path = Path(output) if output else Path(f"health_report.{report}")
89
+
90
+ if report == "json":
91
+ save_json_report(score, output_path)
92
+ console.print(f"[green]JSON report saved to:[/] {output_path}")
93
+ elif report == "html":
94
+ save_html_report(score, output_path)
95
+ console.print(f"[green]HTML report saved to:[/] {output_path}")
96
+
97
+ # In CI mode with report, also output JSON to stdout
98
+ if ci:
99
+ console.print(generate_json_report(score))
100
+ else:
101
+ # Display rich table output
102
+ _display_health_summary(score, verbose)
103
+
104
+ # CI mode exit code handling
105
+ if ci:
106
+ if score.overall_score < threshold:
107
+ console.print(
108
+ f"\n[red]CI Check Failed:[/] Score {score.overall_score:.1f} is below threshold {threshold}"
109
+ )
110
+ sys.exit(1)
111
+ else:
112
+ console.print(
113
+ f"\n[green]CI Check Passed:[/] Score {score.overall_score:.1f} meets threshold {threshold}"
114
+ )
115
+ sys.exit(0)
116
+
117
+
118
+ def _display_health_summary(score: HealthScore, verbose: bool) -> None:
119
+ """Display the health score summary in the terminal.
120
+
121
+ Args:
122
+ score: HealthScore object
123
+ verbose: Whether to show detailed output
124
+ """
125
+ # Grade panel
126
+ grade_style = _get_grade_style(score.grade)
127
+ console.print(
128
+ Panel(
129
+ f"[{grade_style}]Grade {score.grade.value}[/] - {score.overall_score:.1f}/100",
130
+ title="[bold]Codebase Health Score[/]",
131
+ subtitle=str(score.project_path),
132
+ )
133
+ )
134
+
135
+ # Metrics table
136
+ table = Table(title="Metrics Breakdown", show_header=True)
137
+ table.add_column("Category", style="cyan")
138
+ table.add_column("Score", justify="right")
139
+ table.add_column("Weight", justify="right", style="dim")
140
+ table.add_column("Details")
141
+
142
+ # Sort by score (lowest first) to highlight problem areas
143
+ sorted_metrics = sorted(score.metrics, key=lambda m: m.score)
144
+
145
+ for metric in sorted_metrics:
146
+ score_style = _get_score_style(metric.score)
147
+ weight_pct = f"{metric.weight * 100:.0f}%"
148
+ table.add_row(
149
+ _format_category(metric.category),
150
+ f"[{score_style}]{metric.score:.1f}[/]",
151
+ weight_pct,
152
+ metric.description,
153
+ )
154
+
155
+ console.print(table)
156
+
157
+ # Recommendations
158
+ if score.top_recommendations:
159
+ console.print("\n[bold]Top Recommendations:[/]")
160
+ for i, rec in enumerate(score.top_recommendations, 1):
161
+ console.print(f" {i}. {rec}")
162
+
163
+ # Verbose: show dependencies
164
+ if verbose and score.dependencies:
165
+ console.print()
166
+ deps_table = Table(title="Dependencies", show_header=True)
167
+ deps_table.add_column("Package", style="cyan")
168
+ deps_table.add_column("Current")
169
+ deps_table.add_column("Latest")
170
+ deps_table.add_column("Status")
171
+ deps_table.add_column("Migration")
172
+ deps_table.add_column("Vulns", justify="right")
173
+
174
+ for dep in score.dependencies:
175
+ status = "[green]✓[/]" if not dep.is_outdated else "[yellow]↑[/]"
176
+ tier = (
177
+ "[green]Tier 1[/]"
178
+ if dep.has_tier1_support
179
+ else ("[cyan]Tier 2[/]" if dep.has_tier2_support else "[dim]-[/]")
180
+ )
181
+ vuln_count = len(dep.vulnerabilities)
182
+ vuln_style = "green" if vuln_count == 0 else "red"
183
+
184
+ deps_table.add_row(
185
+ dep.name,
186
+ dep.current_version or "?",
187
+ dep.latest_version or "?",
188
+ status,
189
+ tier,
190
+ f"[{vuln_style}]{vuln_count}[/]",
191
+ )
192
+
193
+ console.print(deps_table)
194
+
195
+ # Show vulnerabilities summary if any
196
+ if score.vulnerabilities:
197
+ console.print()
198
+ console.print(
199
+ f"[bold red]Security Alert:[/] {len(score.vulnerabilities)} vulnerabilities found"
200
+ )
201
+ for vuln in score.vulnerabilities[:3]:
202
+ console.print(
203
+ f" - [{vuln.severity.value.upper()}] {vuln.package}: {vuln.vulnerability_id}"
204
+ )
205
+ if len(score.vulnerabilities) > 3:
206
+ console.print(f" ... and {len(score.vulnerabilities) - 3} more")
207
+
208
+
209
+ def _get_grade_style(grade: HealthGrade) -> str:
210
+ """Get Rich style for a grade."""
211
+ styles = {
212
+ HealthGrade.A: "bold green",
213
+ HealthGrade.B: "bold cyan",
214
+ HealthGrade.C: "bold yellow",
215
+ HealthGrade.D: "bold orange1",
216
+ HealthGrade.F: "bold red",
217
+ }
218
+ return styles.get(grade, "white")
219
+
220
+
221
+ def _get_score_style(score: float) -> str:
222
+ """Get Rich style for a numeric score."""
223
+ if score >= 90:
224
+ return "green"
225
+ elif score >= 80:
226
+ return "cyan"
227
+ elif score >= 70:
228
+ return "yellow"
229
+ elif score >= 60:
230
+ return "orange1"
231
+ else:
232
+ return "red"
233
+
234
+
235
+ def _format_category(category: MetricCategory) -> str:
236
+ """Format category for display."""
237
+ names = {
238
+ MetricCategory.FRESHNESS: "Freshness",
239
+ MetricCategory.SECURITY: "Security",
240
+ MetricCategory.MIGRATION_READINESS: "Migration Ready",
241
+ MetricCategory.TEST_COVERAGE: "Test Coverage",
242
+ MetricCategory.DOCUMENTATION: "Documentation",
243
+ }
244
+ return names.get(category, category.value)
@@ -15,6 +15,7 @@ from codeshift.cli.commands.auth import (
15
15
  whoami,
16
16
  )
17
17
  from codeshift.cli.commands.diff import diff
18
+ from codeshift.cli.commands.health import health
18
19
  from codeshift.cli.commands.scan import scan
19
20
  from codeshift.cli.commands.upgrade import upgrade
20
21
  from codeshift.cli.commands.upgrade_all import upgrade_all
@@ -46,6 +47,7 @@ cli.add_command(upgrade)
46
47
  cli.add_command(upgrade_all)
47
48
  cli.add_command(diff)
48
49
  cli.add_command(apply)
50
+ cli.add_command(health)
49
51
 
50
52
  # Auth commands
51
53
  cli.add_command(register)
@@ -0,0 +1,50 @@
1
+ """Codebase health scoring module.
2
+
3
+ This module provides health scoring capabilities for Python projects,
4
+ analyzing dependency freshness, security, migration readiness, test
5
+ coverage, and documentation quality.
6
+
7
+ Example:
8
+ >>> from codeshift.health import HealthCalculator
9
+ >>> calculator = HealthCalculator()
10
+ >>> score = calculator.calculate(Path("."))
11
+ >>> print(score.summary)
12
+ 🟢 Grade A (92.5/100)
13
+ """
14
+
15
+ from codeshift.health.calculator import HealthCalculator
16
+ from codeshift.health.models import (
17
+ DependencyHealth,
18
+ HealthGrade,
19
+ HealthReport,
20
+ HealthScore,
21
+ MetricCategory,
22
+ MetricResult,
23
+ SecurityVulnerability,
24
+ VulnerabilitySeverity,
25
+ )
26
+ from codeshift.health.report import (
27
+ generate_html_report,
28
+ generate_json_report,
29
+ save_html_report,
30
+ save_json_report,
31
+ )
32
+
33
+ __all__ = [
34
+ # Main calculator
35
+ "HealthCalculator",
36
+ # Models
37
+ "DependencyHealth",
38
+ "HealthGrade",
39
+ "HealthReport",
40
+ "HealthScore",
41
+ "MetricCategory",
42
+ "MetricResult",
43
+ "SecurityVulnerability",
44
+ "VulnerabilitySeverity",
45
+ # Report functions
46
+ "generate_html_report",
47
+ "generate_json_report",
48
+ "save_html_report",
49
+ "save_json_report",
50
+ ]
@@ -0,0 +1,217 @@
1
+ """Main health score calculator orchestrator."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from codeshift.health.metrics import BaseMetricCalculator
7
+ from codeshift.health.metrics.documentation import DocumentationCalculator
8
+ from codeshift.health.metrics.freshness import FreshnessCalculator
9
+ from codeshift.health.metrics.migration_readiness import MigrationReadinessCalculator
10
+ from codeshift.health.metrics.security import SecurityCalculator
11
+ from codeshift.health.metrics.test_coverage import TestCoverageCalculator
12
+ from codeshift.health.models import (
13
+ DependencyHealth,
14
+ HealthGrade,
15
+ HealthReport,
16
+ HealthScore,
17
+ MetricResult,
18
+ SecurityVulnerability,
19
+ )
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class HealthCalculator:
25
+ """Orchestrates health score calculation across all metrics."""
26
+
27
+ def __init__(self) -> None:
28
+ """Initialize the calculator with all metric calculators."""
29
+ self.calculators: list[BaseMetricCalculator] = [
30
+ FreshnessCalculator(),
31
+ SecurityCalculator(),
32
+ MigrationReadinessCalculator(),
33
+ TestCoverageCalculator(),
34
+ DocumentationCalculator(),
35
+ ]
36
+
37
+ def calculate(self, project_path: Path) -> HealthScore:
38
+ """Calculate the complete health score for a project.
39
+
40
+ Args:
41
+ project_path: Path to the project root
42
+
43
+ Returns:
44
+ HealthScore with all metrics and overall score
45
+ """
46
+ project_path = project_path.resolve()
47
+
48
+ # First, analyze dependencies once to share across calculators
49
+ dependencies = self._analyze_dependencies(project_path)
50
+
51
+ # Calculate each metric
52
+ metrics: list[MetricResult] = []
53
+ for calculator in self.calculators:
54
+ try:
55
+ result = calculator.calculate(
56
+ project_path,
57
+ dependencies=dependencies,
58
+ )
59
+ metrics.append(result)
60
+ except Exception as e:
61
+ logger.warning(f"Failed to calculate {calculator.category.value}: {e}")
62
+ # Add a neutral result on failure
63
+ metrics.append(
64
+ MetricResult(
65
+ category=calculator.category,
66
+ score=50,
67
+ weight=calculator.weight,
68
+ description=f"Error: {str(e)[:50]}",
69
+ details={"error": str(e)},
70
+ recommendations=["Fix metric calculation error"],
71
+ )
72
+ )
73
+
74
+ # Calculate overall weighted score
75
+ total_weight = sum(m.weight for m in metrics)
76
+ if total_weight > 0:
77
+ overall_score = sum(m.weighted_score for m in metrics) / total_weight
78
+ else:
79
+ overall_score = 0
80
+
81
+ # Collect all vulnerabilities
82
+ all_vulns: list[SecurityVulnerability] = []
83
+ for dep in dependencies:
84
+ all_vulns.extend(dep.vulnerabilities)
85
+
86
+ return HealthScore(
87
+ overall_score=overall_score,
88
+ grade=HealthGrade.from_score(overall_score),
89
+ metrics=metrics,
90
+ dependencies=dependencies,
91
+ vulnerabilities=all_vulns,
92
+ project_path=project_path,
93
+ )
94
+
95
+ def calculate_report(
96
+ self,
97
+ project_path: Path,
98
+ previous: HealthScore | None = None,
99
+ ) -> HealthReport:
100
+ """Calculate a health report with trend information.
101
+
102
+ Args:
103
+ project_path: Path to the project root
104
+ previous: Optional previous health score for comparison
105
+
106
+ Returns:
107
+ HealthReport with current score and trend
108
+ """
109
+ current = self.calculate(project_path)
110
+ return HealthReport(current=current, previous=previous)
111
+
112
+ def _analyze_dependencies(self, project_path: Path) -> list[DependencyHealth]:
113
+ """Analyze all dependencies for shared data.
114
+
115
+ This method runs once and provides data for multiple calculators
116
+ to avoid redundant API calls.
117
+
118
+ Args:
119
+ project_path: Path to the project
120
+
121
+ Returns:
122
+ List of DependencyHealth with all analyzable data
123
+ """
124
+ from codeshift.scanner.dependency_parser import DependencyParser
125
+
126
+ parser = DependencyParser(project_path)
127
+ raw_deps = parser.parse_all()
128
+
129
+ # Get knowledge base info for tier support
130
+ from codeshift.knowledge_base import KnowledgeBaseLoader
131
+
132
+ loader = KnowledgeBaseLoader()
133
+ supported_libraries = loader.get_supported_libraries()
134
+ tier1_libraries = {"pydantic", "fastapi", "sqlalchemy", "pandas", "requests"}
135
+
136
+ dependencies: list[DependencyHealth] = []
137
+
138
+ for dep in raw_deps:
139
+ dep_name_lower = dep.name.lower()
140
+
141
+ # Get latest version and vulnerabilities from PyPI
142
+ latest_version = None
143
+ vulnerabilities: list[SecurityVulnerability] = []
144
+
145
+ try:
146
+ import httpx
147
+ from packaging.version import Version
148
+
149
+ response = httpx.get(
150
+ f"https://pypi.org/pypi/{dep.name}/json",
151
+ timeout=5.0,
152
+ )
153
+ if response.status_code == 200:
154
+ data = response.json()
155
+
156
+ # Get latest version
157
+ version_str = data.get("info", {}).get("version")
158
+ if version_str:
159
+ latest_version = Version(version_str)
160
+
161
+ # Get vulnerabilities
162
+ from codeshift.health.models import VulnerabilitySeverity
163
+
164
+ for vuln_data in data.get("vulnerabilities", []):
165
+ try:
166
+ severity = VulnerabilitySeverity.MEDIUM
167
+ vulnerabilities.append(
168
+ SecurityVulnerability(
169
+ package=dep.name,
170
+ vulnerability_id=vuln_data.get("id", "unknown"),
171
+ severity=severity,
172
+ description=vuln_data.get("summary", "")[:200],
173
+ fixed_in=(
174
+ vuln_data.get("fixed_in", [None])[0]
175
+ if vuln_data.get("fixed_in")
176
+ else None
177
+ ),
178
+ url=vuln_data.get("link"),
179
+ )
180
+ )
181
+ except Exception:
182
+ pass
183
+
184
+ except Exception as e:
185
+ logger.debug(f"Failed to fetch PyPI data for {dep.name}: {e}")
186
+
187
+ # Calculate version lag
188
+ current = dep.min_version
189
+ is_outdated = False
190
+ major_behind = 0
191
+ minor_behind = 0
192
+
193
+ if current and latest_version:
194
+ is_outdated = current < latest_version
195
+ major_behind = max(0, latest_version.major - current.major)
196
+ if major_behind == 0:
197
+ minor_behind = max(0, latest_version.minor - current.minor)
198
+
199
+ # Check tier support
200
+ has_tier1 = dep_name_lower in tier1_libraries
201
+ has_tier2 = dep_name_lower in [lib.lower() for lib in supported_libraries]
202
+
203
+ dependencies.append(
204
+ DependencyHealth(
205
+ name=dep.name,
206
+ current_version=str(current) if current else None,
207
+ latest_version=str(latest_version) if latest_version else None,
208
+ is_outdated=is_outdated,
209
+ major_versions_behind=major_behind,
210
+ minor_versions_behind=minor_behind,
211
+ has_tier1_support=has_tier1,
212
+ has_tier2_support=has_tier2,
213
+ vulnerabilities=vulnerabilities,
214
+ )
215
+ )
216
+
217
+ return dependencies