sourcecode 1.60.0__py3-none-any.whl → 1.62.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +7 -0
- sourcecode/hibernate_strat.py +599 -0
- sourcecode/mcp/registry.py +6 -3
- sourcecode/mcp/server.py +4 -1
- sourcecode/migrate_check.py +223 -32
- sourcecode/serializer.py +44 -1
- {sourcecode-1.60.0.dist-info → sourcecode-1.62.0.dist-info}/METADATA +1 -1
- {sourcecode-1.60.0.dist-info → sourcecode-1.62.0.dist-info}/RECORD +12 -11
- {sourcecode-1.60.0.dist-info → sourcecode-1.62.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.60.0.dist-info → sourcecode-1.62.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.60.0.dist-info → sourcecode-1.62.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -4838,6 +4838,13 @@ def migrate_check_cmd(
|
|
|
4838
4838
|
MIG-007 javax.inject import (MEDIUM)
|
|
4839
4839
|
MIG-008 javax.ws.rs import (MEDIUM — JAX-RS changed)
|
|
4840
4840
|
|
|
4841
|
+
\b
|
|
4842
|
+
Hibernate 5→6 stratification (in the 'hibernate' output section):
|
|
4843
|
+
4 independent layers (JPA annotations / Criteria / HQL / SPI), a per-layer
|
|
4844
|
+
risk matrix, a module exposure map, critical call-chain detection, and an
|
|
4845
|
+
UPGRADE vs REWRITE verdict (dynamic Criteria, custom SPI, reflection-built
|
|
4846
|
+
queries, or concatenated query strings force the REWRITE classification).
|
|
4847
|
+
|
|
4841
4848
|
\b
|
|
4842
4849
|
Examples:
|
|
4843
4850
|
sourcecode migrate-check .
|
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
"""hibernate_strat.py — Hibernate 5.x → 6.x migration stratification model.
|
|
2
|
+
|
|
3
|
+
A Hibernate major upgrade is NOT a single dependency bump for enterprise systems
|
|
4
|
+
that use dynamic persistence. This module splits Hibernate exposure into four
|
|
5
|
+
independent migration domains and classifies each one on its own risk axis:
|
|
6
|
+
|
|
7
|
+
1. JPA Annotation Layer — LOW (unless deprecated annotations exist)
|
|
8
|
+
2. Criteria API Layer — HIGH, escalates to CRITICAL when built
|
|
9
|
+
dynamically via reflection / abstraction DAOs
|
|
10
|
+
3. HQL / String-based Queries — MEDIUM, escalates to HIGH on concatenation
|
|
11
|
+
4. Hibernate SPI / Internal — CRITICAL BLOCKER (UserType, Interceptor, SPI)
|
|
12
|
+
|
|
13
|
+
It also produces:
|
|
14
|
+
- a module-level Hibernate exposure map,
|
|
15
|
+
- critical call-chain detection (DynamicEntityDao, BasicPersistenceModule, …),
|
|
16
|
+
- stop-condition logic that decides UPGRADE ZONE vs REWRITE ZONE,
|
|
17
|
+
- separation of observable code risk from inferred runtime risk.
|
|
18
|
+
|
|
19
|
+
Entry point: analyze_hibernate(file_paths, root) → HibernateStratification
|
|
20
|
+
|
|
21
|
+
This is purely additive: it does not aggregate Hibernate into a single score, and
|
|
22
|
+
it does not assume that a Spring Boot upgrade resolves ORM risk.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Layer identifiers
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
LAYER_JPA = "jpa_annotations"
|
|
37
|
+
LAYER_CRITERIA = "criteria_api"
|
|
38
|
+
LAYER_HQL = "hql_string_queries"
|
|
39
|
+
LAYER_SPI = "hibernate_spi_internal"
|
|
40
|
+
|
|
41
|
+
_LAYER_TITLES: dict[str, str] = {
|
|
42
|
+
LAYER_JPA: "JPA Annotation Layer",
|
|
43
|
+
LAYER_CRITERIA: "Criteria API Layer",
|
|
44
|
+
LAYER_HQL: "HQL / String-based Queries",
|
|
45
|
+
LAYER_SPI: "Hibernate SPI / Internal API",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Classification verdicts
|
|
49
|
+
CLASS_NONE = "none"
|
|
50
|
+
CLASS_UPGRADE = "upgrade_zone"
|
|
51
|
+
CLASS_UPGRADE_CARE = "upgrade_with_care"
|
|
52
|
+
CLASS_REWRITE = "rewrite_zone"
|
|
53
|
+
|
|
54
|
+
_CLASS_LABELS: dict[str, str] = {
|
|
55
|
+
CLASS_NONE: "NO HIBERNATE USAGE DETECTED",
|
|
56
|
+
CLASS_UPGRADE: "HIBERNATE 6 MIGRATION IS UPGRADE ZONE (dependency bump + spot fixes)",
|
|
57
|
+
CLASS_UPGRADE_CARE: "HIBERNATE 6 MIGRATION IS UPGRADE-WITH-CARE ZONE (static queries to revalidate)",
|
|
58
|
+
CLASS_REWRITE: "HIBERNATE 6 MIGRATION IS HIGH RISK REWRITE ZONE (NOT UPGRADE ZONE)",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_SEVERITY_RANK: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Detection patterns
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
# Layer 1 — standard JPA annotations (LOW baseline).
|
|
69
|
+
_JPA_ANNOTATION_RE = re.compile(
|
|
70
|
+
r"@(Entity|Table|OneToMany|ManyToOne|OneToOne|ManyToMany|Column|"
|
|
71
|
+
r"Id|GeneratedValue|MappedSuperclass|Embeddable|Embedded|JoinColumn|"
|
|
72
|
+
r"JoinTable|Inheritance|DiscriminatorColumn)\b"
|
|
73
|
+
)
|
|
74
|
+
# Hibernate / JPA annotations deprecated or reworked in Hibernate 6 (escalate).
|
|
75
|
+
_JPA_DEPRECATED_RE = re.compile(
|
|
76
|
+
r"@TypeDef\b|@TypeDefs\b|"
|
|
77
|
+
r"org\.hibernate\.annotations\.Type\b|"
|
|
78
|
+
r"@Type\s*\(\s*type\s*=|"
|
|
79
|
+
r"@GenericGenerator\b|"
|
|
80
|
+
r"@org\.hibernate\.annotations\.Entity\b"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Layer 2 — JPA Criteria API + legacy Hibernate Criteria.
|
|
84
|
+
_CRITERIA_JPA_RE = re.compile(
|
|
85
|
+
r"\bCriteriaBuilder\b|\bCriteriaQuery\b|\bCriteriaUpdate\b|"
|
|
86
|
+
r"\bCriteriaDelete\b|\bRoot\s*<|\bPredicate\b|\.criteria\b|"
|
|
87
|
+
r"persistence\.criteria"
|
|
88
|
+
)
|
|
89
|
+
_CRITERIA_LEGACY_RE = re.compile(
|
|
90
|
+
r"org\.hibernate\.Criteria\b|\.createCriteria\s*\(|\bDetachedCriteria\b|"
|
|
91
|
+
r"\bRestrictions\.|\bProjections\.|\bCriterion\b|"
|
|
92
|
+
r"\bConjunction\b|\bDisjunction\b"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Layer 3 — HQL / native string queries. createQuery requires a string literal so
|
|
96
|
+
# CriteriaBuilder.createQuery(Class) (Layer 2) is not double-counted here.
|
|
97
|
+
_HQL_RE = re.compile(
|
|
98
|
+
r"\.createQuery\s*\(\s*\"|"
|
|
99
|
+
r"\.(createSQLQuery|createNativeQuery|getNamedQuery|createNamedQuery)\s*\("
|
|
100
|
+
)
|
|
101
|
+
# String concatenation inside a query call → dynamic SQL shape (HIGH).
|
|
102
|
+
_HQL_CONCAT_RE = re.compile(
|
|
103
|
+
r"\.(?:createQuery|createSQLQuery|createNativeQuery)\s*\("
|
|
104
|
+
r"[^;]{0,400}?(?:\"[^\"]*\"\s*\+|\+\s*\"[^\"]*\"|\+\s*\w+\s*\+)",
|
|
105
|
+
re.DOTALL,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Layer 4 — Hibernate SPI / internal API (CRITICAL blocker).
|
|
109
|
+
_SPI_RE = re.compile(
|
|
110
|
+
r"\b(?:implements|extends)\s+\w*(?:UserType|CompositeUserType|UserCollectionType)\b|"
|
|
111
|
+
r"\bimplements\s+\w*UserType\b|"
|
|
112
|
+
r"\borg\.hibernate\.(?:type|engine|internal|persister|metamodel|"
|
|
113
|
+
r"boot\.spi|boot\.internal|event|tuple|property\.access|loader|sql\.ast)\b|"
|
|
114
|
+
r"\bEmptyInterceptor\b|"
|
|
115
|
+
r"\bimplements\s+\w*Interceptor\b|"
|
|
116
|
+
r"\b(?:implements|extends)\s+\w*EventListener\b|"
|
|
117
|
+
r"\bSessionFactoryImpl\b|\bSessionImplementor\b|"
|
|
118
|
+
r"\bSharedSessionContractImplementor\b|"
|
|
119
|
+
r"\bsetPropertyAccessStrategy\b|\bPropertyAccessStrategy\b|"
|
|
120
|
+
r"\bImplicitNamingStrategy\b|\bPhysicalNamingStrategy\b"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Escalation markers — dynamic / reflection-based persistence construction.
|
|
124
|
+
_ABSTRACTION_CLASS_RE = re.compile(
|
|
125
|
+
r"\b(DynamicEntityDao|DynamicEntityDaoImpl|BasicPersistenceModule|"
|
|
126
|
+
r"PersistenceModule|GenericDao|GenericEntityDao|GenericEntityService|"
|
|
127
|
+
r"DynamicDaoHelper|CriteriaTranslator|FieldManager|EntityMetadata)\w*\b"
|
|
128
|
+
)
|
|
129
|
+
_REFLECTION_RE = re.compile(
|
|
130
|
+
r"Class\.forName\s*\(|\.getDeclaredField|\.getDeclaredMethod|"
|
|
131
|
+
r"\.getDeclaredFields\s*\(|\.newInstance\s*\(|Method\.invoke|"
|
|
132
|
+
r"java\.lang\.reflect|\bField\[\]|metadata\.get|buildCriteria|"
|
|
133
|
+
r"\.getMetamodel\s*\("
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Data model
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class HibernateFinding:
|
|
143
|
+
layer: str
|
|
144
|
+
severity: str
|
|
145
|
+
pattern: str # short human label of what matched
|
|
146
|
+
source_file: str
|
|
147
|
+
first_line: int
|
|
148
|
+
module: str
|
|
149
|
+
occurrences: int = 1
|
|
150
|
+
dynamic: bool = False # escalated via reflection / abstraction DAO context
|
|
151
|
+
|
|
152
|
+
def to_dict(self) -> dict:
|
|
153
|
+
return {
|
|
154
|
+
"layer": self.layer,
|
|
155
|
+
"severity": self.severity,
|
|
156
|
+
"pattern": self.pattern,
|
|
157
|
+
"source_file": self.source_file,
|
|
158
|
+
"first_line": self.first_line,
|
|
159
|
+
"module": self.module,
|
|
160
|
+
"occurrences": self.occurrences,
|
|
161
|
+
"dynamic": self.dynamic,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class HibernateLayerRisk:
|
|
167
|
+
layer: str
|
|
168
|
+
risk: str
|
|
169
|
+
reason: str
|
|
170
|
+
estimated_effort: str
|
|
171
|
+
file_count: int
|
|
172
|
+
occurrence_count: int
|
|
173
|
+
|
|
174
|
+
def to_dict(self) -> dict:
|
|
175
|
+
return {
|
|
176
|
+
"layer": self.layer,
|
|
177
|
+
"layer_title": _LAYER_TITLES.get(self.layer, self.layer),
|
|
178
|
+
"risk": self.risk,
|
|
179
|
+
"reason": self.reason,
|
|
180
|
+
"estimated_effort": self.estimated_effort,
|
|
181
|
+
"file_count": self.file_count,
|
|
182
|
+
"occurrence_count": self.occurrence_count,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class HibernateStratification:
|
|
188
|
+
detected: bool = False
|
|
189
|
+
classification: str = CLASS_NONE
|
|
190
|
+
classification_label: str = _CLASS_LABELS[CLASS_NONE]
|
|
191
|
+
risk_matrix: list[HibernateLayerRisk] = field(default_factory=list)
|
|
192
|
+
module_exposure: dict[str, dict] = field(default_factory=dict)
|
|
193
|
+
incompatible_patterns: list[str] = field(default_factory=list)
|
|
194
|
+
critical_call_chains: list[dict] = field(default_factory=list)
|
|
195
|
+
stop_conditions_triggered: list[str] = field(default_factory=list)
|
|
196
|
+
observable_risk: list[str] = field(default_factory=list)
|
|
197
|
+
inferred_runtime_risk: list[str] = field(default_factory=list)
|
|
198
|
+
findings: list[HibernateFinding] = field(default_factory=list)
|
|
199
|
+
|
|
200
|
+
def to_dict(self) -> dict:
|
|
201
|
+
return {
|
|
202
|
+
"detected": self.detected,
|
|
203
|
+
"classification": self.classification,
|
|
204
|
+
"classification_label": self.classification_label,
|
|
205
|
+
"stratified": True,
|
|
206
|
+
"risk_matrix": [r.to_dict() for r in self.risk_matrix],
|
|
207
|
+
"module_exposure_map": self.module_exposure,
|
|
208
|
+
"incompatible_patterns": self.incompatible_patterns,
|
|
209
|
+
"critical_call_chains": self.critical_call_chains,
|
|
210
|
+
"stop_conditions_triggered": self.stop_conditions_triggered,
|
|
211
|
+
"risk_separation": {
|
|
212
|
+
"observable_code_risk": self.observable_risk,
|
|
213
|
+
"inferred_runtime_risk": self.inferred_runtime_risk,
|
|
214
|
+
},
|
|
215
|
+
"findings": [f.to_dict() for f in self.findings],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
def to_text(self) -> str:
|
|
219
|
+
if not self.detected:
|
|
220
|
+
return "Hibernate Migration: no Hibernate/JPA persistence usage detected."
|
|
221
|
+
|
|
222
|
+
lines: list[str] = [
|
|
223
|
+
"── Hibernate 5.x → 6.x Migration Stratification ──",
|
|
224
|
+
f"Classification: {self.classification_label}",
|
|
225
|
+
"",
|
|
226
|
+
"Hibernate Migration Impact Matrix:",
|
|
227
|
+
f" {'Layer':<30} {'Risk':<9} Effort Reason",
|
|
228
|
+
]
|
|
229
|
+
for r in self.risk_matrix:
|
|
230
|
+
lines.append(
|
|
231
|
+
f" {_LAYER_TITLES.get(r.layer, r.layer):<30} "
|
|
232
|
+
f"{r.risk.upper():<9} {r.estimated_effort:<13} {r.reason}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
lines.append("")
|
|
236
|
+
lines.append("Module Hibernate Exposure Map:")
|
|
237
|
+
if self.module_exposure:
|
|
238
|
+
for mod, info in sorted(
|
|
239
|
+
self.module_exposure.items(),
|
|
240
|
+
key=lambda kv: _SEVERITY_RANK.get(kv[1].get("max_risk", "low"), 3),
|
|
241
|
+
):
|
|
242
|
+
tags = []
|
|
243
|
+
if info.get("criteria_dynamic"):
|
|
244
|
+
tags.append("dynamic-criteria")
|
|
245
|
+
if info.get("has_spi"):
|
|
246
|
+
tags.append("custom-SPI")
|
|
247
|
+
if info.get("has_reflection"):
|
|
248
|
+
tags.append("reflection")
|
|
249
|
+
tag_str = f" [{', '.join(tags)}]" if tags else ""
|
|
250
|
+
layers = ", ".join(sorted(info.get("layers", {}).keys()))
|
|
251
|
+
lines.append(
|
|
252
|
+
f" {mod:<40} {info.get('max_risk', 'low').upper():<9} "
|
|
253
|
+
f"{layers}{tag_str}"
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
lines.append(" (no module attribution)")
|
|
257
|
+
|
|
258
|
+
if self.critical_call_chains:
|
|
259
|
+
lines.append("")
|
|
260
|
+
lines.append("Critical Call Chains (dynamic / reflection-based persistence):")
|
|
261
|
+
for cc in self.critical_call_chains:
|
|
262
|
+
lines.append(
|
|
263
|
+
f" {cc['class']} ({cc['source_file']}:{cc['first_line']}) — {cc['reason']}"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if self.incompatible_patterns:
|
|
267
|
+
lines.append("")
|
|
268
|
+
lines.append("Incompatible / High-risk Patterns Found:")
|
|
269
|
+
for p in self.incompatible_patterns:
|
|
270
|
+
lines.append(f" - {p}")
|
|
271
|
+
|
|
272
|
+
if self.stop_conditions_triggered:
|
|
273
|
+
lines.append("")
|
|
274
|
+
lines.append("Stop Conditions Triggered (force REWRITE classification):")
|
|
275
|
+
for s in self.stop_conditions_triggered:
|
|
276
|
+
lines.append(f" ! {s}")
|
|
277
|
+
|
|
278
|
+
lines.append("")
|
|
279
|
+
lines.append("Risk Separation:")
|
|
280
|
+
lines.append(" Observable code risk:")
|
|
281
|
+
for o in (self.observable_risk or [" (none)"]):
|
|
282
|
+
lines.append(f" - {o}")
|
|
283
|
+
lines.append(" Inferred runtime risk:")
|
|
284
|
+
for o in (self.inferred_runtime_risk or [" (none)"]):
|
|
285
|
+
lines.append(f" - {o}")
|
|
286
|
+
|
|
287
|
+
return "\n".join(lines)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ---------------------------------------------------------------------------
|
|
291
|
+
# Helpers
|
|
292
|
+
# ---------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
def _module_of(rel_path: str) -> str:
|
|
295
|
+
"""Derive a Maven/Gradle module name from a source path.
|
|
296
|
+
|
|
297
|
+
Uses the directory segment immediately before '/src/' (the module root). Falls
|
|
298
|
+
back to the first path segment, or 'root' for top-level files.
|
|
299
|
+
"""
|
|
300
|
+
norm = rel_path.replace("\\", "/")
|
|
301
|
+
parts = norm.split("/")
|
|
302
|
+
for i, seg in enumerate(parts):
|
|
303
|
+
if seg == "src" and i > 0:
|
|
304
|
+
return parts[i - 1]
|
|
305
|
+
if len(parts) > 1:
|
|
306
|
+
return parts[0]
|
|
307
|
+
return "root"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _first_line(source: str, match: re.Match) -> int:
|
|
311
|
+
return source[: match.start()].count("\n") + 1
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _count(pattern: re.Pattern, source: str) -> int:
|
|
315
|
+
return sum(1 for _ in pattern.finditer(source))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
# Effort heuristics
|
|
320
|
+
# ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
def _effort_bucket(file_count: int, base_days_per_file: float, floor: float = 0.5) -> str:
|
|
323
|
+
days = round(max(floor, file_count * base_days_per_file), 1) if file_count else 0.0
|
|
324
|
+
return f"~{days}d"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
# Public entry point
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratification:
|
|
332
|
+
"""Scan Java sources for stratified Hibernate 5→6 migration risk."""
|
|
333
|
+
findings: list[HibernateFinding] = []
|
|
334
|
+
# module -> aggregation accumulator
|
|
335
|
+
modules: dict[str, dict] = {}
|
|
336
|
+
call_chains: list[dict] = []
|
|
337
|
+
|
|
338
|
+
layer_files: dict[str, set[str]] = {
|
|
339
|
+
LAYER_JPA: set(), LAYER_CRITERIA: set(), LAYER_HQL: set(), LAYER_SPI: set()
|
|
340
|
+
}
|
|
341
|
+
layer_occ: dict[str, int] = {
|
|
342
|
+
LAYER_JPA: 0, LAYER_CRITERIA: 0, LAYER_HQL: 0, LAYER_SPI: 0
|
|
343
|
+
}
|
|
344
|
+
jpa_deprecated_seen = False
|
|
345
|
+
criteria_dynamic_global = False
|
|
346
|
+
spi_global = False
|
|
347
|
+
reflection_query_global = False
|
|
348
|
+
concat_query_global = False
|
|
349
|
+
|
|
350
|
+
incompatible: set[str] = set()
|
|
351
|
+
|
|
352
|
+
for rel_path in file_paths:
|
|
353
|
+
if not rel_path.endswith(".java"):
|
|
354
|
+
continue
|
|
355
|
+
abs_path = root / rel_path
|
|
356
|
+
try:
|
|
357
|
+
source = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
358
|
+
except OSError:
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
# Per-file escalation context.
|
|
362
|
+
has_abstraction = bool(_ABSTRACTION_CLASS_RE.search(source))
|
|
363
|
+
has_reflection = bool(_REFLECTION_RE.search(source))
|
|
364
|
+
module = _module_of(rel_path)
|
|
365
|
+
mod = modules.setdefault(
|
|
366
|
+
module,
|
|
367
|
+
{"layers": {}, "max_risk": "low", "criteria_dynamic": False,
|
|
368
|
+
"has_spi": False, "has_reflection": False, "files": set()},
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def _record(layer: str, severity: str, pattern_label: str,
|
|
372
|
+
match: re.Match, occ: int, dynamic: bool = False) -> None:
|
|
373
|
+
findings.append(HibernateFinding(
|
|
374
|
+
layer=layer, severity=severity, pattern=pattern_label,
|
|
375
|
+
source_file=rel_path, first_line=_first_line(source, match),
|
|
376
|
+
module=module, occurrences=occ, dynamic=dynamic,
|
|
377
|
+
))
|
|
378
|
+
layer_files[layer].add(rel_path)
|
|
379
|
+
layer_occ[layer] += occ
|
|
380
|
+
ml = mod["layers"].setdefault(layer, {"severity": severity, "occurrences": 0})
|
|
381
|
+
ml["occurrences"] += occ
|
|
382
|
+
if _SEVERITY_RANK[severity] < _SEVERITY_RANK[ml["severity"]]:
|
|
383
|
+
ml["severity"] = severity
|
|
384
|
+
if _SEVERITY_RANK[severity] < _SEVERITY_RANK[mod["max_risk"]]:
|
|
385
|
+
mod["max_risk"] = severity
|
|
386
|
+
mod["files"].add(rel_path)
|
|
387
|
+
|
|
388
|
+
if has_reflection:
|
|
389
|
+
mod["has_reflection"] = True
|
|
390
|
+
|
|
391
|
+
# ── Layer 1: JPA annotations ───────────────────────────────────────
|
|
392
|
+
m = _JPA_ANNOTATION_RE.search(source)
|
|
393
|
+
if m:
|
|
394
|
+
_record(LAYER_JPA, "low", "JPA mapping annotations",
|
|
395
|
+
m, _count(_JPA_ANNOTATION_RE, source))
|
|
396
|
+
dm = _JPA_DEPRECATED_RE.search(source)
|
|
397
|
+
if dm:
|
|
398
|
+
jpa_deprecated_seen = True
|
|
399
|
+
_record(LAYER_JPA, "high", "Deprecated Hibernate annotation (@Type/@TypeDef/@GenericGenerator)",
|
|
400
|
+
dm, _count(_JPA_DEPRECATED_RE, source))
|
|
401
|
+
incompatible.add("Deprecated Hibernate annotations (@Type(type=)/@TypeDef) — reworked in Hibernate 6")
|
|
402
|
+
|
|
403
|
+
# ── Layer 2: Criteria API ──────────────────────────────────────────
|
|
404
|
+
crit_m = _CRITERIA_JPA_RE.search(source)
|
|
405
|
+
legacy_m = _CRITERIA_LEGACY_RE.search(source)
|
|
406
|
+
crit_hit = crit_m or legacy_m
|
|
407
|
+
if crit_hit:
|
|
408
|
+
dynamic = has_abstraction or has_reflection
|
|
409
|
+
severity = "critical" if dynamic else "high"
|
|
410
|
+
occ = _count(_CRITERIA_JPA_RE, source) + _count(_CRITERIA_LEGACY_RE, source)
|
|
411
|
+
label = ("Dynamic Criteria construction (reflection/abstraction DAO)"
|
|
412
|
+
if dynamic else "Criteria API usage")
|
|
413
|
+
_record(LAYER_CRITERIA, severity, label, crit_hit, occ, dynamic=dynamic)
|
|
414
|
+
if dynamic:
|
|
415
|
+
criteria_dynamic_global = True
|
|
416
|
+
mod["criteria_dynamic"] = True
|
|
417
|
+
incompatible.add("Criteria built dynamically via reflection/abstraction layer "
|
|
418
|
+
"(Hibernate 6 incompatible patterns likely)")
|
|
419
|
+
if legacy_m:
|
|
420
|
+
incompatible.add("Legacy org.hibernate.Criteria / Restrictions / Projections "
|
|
421
|
+
"(removed in Hibernate 6 — must move to JPA CriteriaBuilder)")
|
|
422
|
+
|
|
423
|
+
# ── Layer 3: HQL / string queries ──────────────────────────────────
|
|
424
|
+
hql_m = _HQL_RE.search(source)
|
|
425
|
+
if hql_m:
|
|
426
|
+
concat_m = _HQL_CONCAT_RE.search(source)
|
|
427
|
+
if concat_m:
|
|
428
|
+
concat_query_global = True
|
|
429
|
+
_record(LAYER_HQL, "high",
|
|
430
|
+
"String-concatenated HQL/native query (dynamic SQL shape)",
|
|
431
|
+
concat_m, _count(_HQL_RE, source))
|
|
432
|
+
incompatible.add("String-concatenated HQL/native queries — HIGH risk under "
|
|
433
|
+
"Hibernate 6 ORM parser changes")
|
|
434
|
+
else:
|
|
435
|
+
_record(LAYER_HQL, "medium", "HQL / native string query",
|
|
436
|
+
hql_m, _count(_HQL_RE, source))
|
|
437
|
+
|
|
438
|
+
# ── Layer 4: Hibernate SPI / internal ──────────────────────────────
|
|
439
|
+
spi_m = _SPI_RE.search(source)
|
|
440
|
+
if spi_m:
|
|
441
|
+
spi_global = True
|
|
442
|
+
mod["has_spi"] = True
|
|
443
|
+
_record(LAYER_SPI, "critical", "Hibernate SPI / internal API (UserType/Interceptor/SPI)",
|
|
444
|
+
spi_m, _count(_SPI_RE, source))
|
|
445
|
+
incompatible.add("Custom Hibernate SPI (UserType/CompositeUserType/Interceptor/"
|
|
446
|
+
"EventListener) — primary Hibernate 5→6 breakage source")
|
|
447
|
+
|
|
448
|
+
# ── Critical call-chain detection ──────────────────────────────────
|
|
449
|
+
if has_abstraction and (crit_hit or has_reflection):
|
|
450
|
+
am = _ABSTRACTION_CLASS_RE.search(source)
|
|
451
|
+
reason_bits = []
|
|
452
|
+
if crit_hit:
|
|
453
|
+
reason_bits.append("Criteria construction")
|
|
454
|
+
if has_reflection:
|
|
455
|
+
reason_bits.append("reflection-based entity metadata")
|
|
456
|
+
reflection_query_global = True
|
|
457
|
+
call_chains.append({
|
|
458
|
+
"class": am.group(0),
|
|
459
|
+
"source_file": rel_path,
|
|
460
|
+
"first_line": _first_line(source, am),
|
|
461
|
+
"reason": "dynamic query generation: " + " + ".join(reason_bits),
|
|
462
|
+
"module": module,
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
strat = HibernateStratification()
|
|
466
|
+
strat.findings = findings
|
|
467
|
+
|
|
468
|
+
if not findings:
|
|
469
|
+
strat.detected = False
|
|
470
|
+
strat.classification = CLASS_NONE
|
|
471
|
+
strat.classification_label = _CLASS_LABELS[CLASS_NONE]
|
|
472
|
+
return strat
|
|
473
|
+
|
|
474
|
+
strat.detected = True
|
|
475
|
+
strat.critical_call_chains = call_chains
|
|
476
|
+
strat.incompatible_patterns = sorted(incompatible)
|
|
477
|
+
|
|
478
|
+
# ── Build per-layer risk matrix ────────────────────────────────────────
|
|
479
|
+
matrix: list[HibernateLayerRisk] = []
|
|
480
|
+
|
|
481
|
+
# Layer 1
|
|
482
|
+
if layer_files[LAYER_JPA]:
|
|
483
|
+
if jpa_deprecated_seen:
|
|
484
|
+
risk, reason = "high", ("Deprecated Hibernate annotations present "
|
|
485
|
+
"(@Type(type=)/@TypeDef/@GenericGenerator reworked in H6)")
|
|
486
|
+
effort = _effort_bucket(len(layer_files[LAYER_JPA]), 0.25)
|
|
487
|
+
else:
|
|
488
|
+
risk, reason = "low", ("Standard JPA mapping only — namespace handled by "
|
|
489
|
+
"jakarta migration, no Hibernate-6 annotation breakage")
|
|
490
|
+
effort = _effort_bucket(len(layer_files[LAYER_JPA]), 0.02, floor=0.2)
|
|
491
|
+
matrix.append(HibernateLayerRisk(
|
|
492
|
+
LAYER_JPA, risk, reason, effort,
|
|
493
|
+
len(layer_files[LAYER_JPA]), layer_occ[LAYER_JPA]))
|
|
494
|
+
|
|
495
|
+
# Layer 2
|
|
496
|
+
if layer_files[LAYER_CRITERIA]:
|
|
497
|
+
if criteria_dynamic_global:
|
|
498
|
+
risk, reason = "critical", ("Criteria constructed dynamically via reflection / "
|
|
499
|
+
"abstraction DAOs — Hibernate 6 rewrite zone")
|
|
500
|
+
effort = _effort_bucket(len(layer_files[LAYER_CRITERIA]), 0.75)
|
|
501
|
+
else:
|
|
502
|
+
risk, reason = "high", ("Criteria API in use — JPA Criteria semantics changed; "
|
|
503
|
+
"legacy org.hibernate.Criteria removed in H6")
|
|
504
|
+
effort = _effort_bucket(len(layer_files[LAYER_CRITERIA]), 0.4)
|
|
505
|
+
matrix.append(HibernateLayerRisk(
|
|
506
|
+
LAYER_CRITERIA, risk, reason, effort,
|
|
507
|
+
len(layer_files[LAYER_CRITERIA]), layer_occ[LAYER_CRITERIA]))
|
|
508
|
+
|
|
509
|
+
# Layer 3
|
|
510
|
+
if layer_files[LAYER_HQL]:
|
|
511
|
+
if concat_query_global:
|
|
512
|
+
risk, reason = "high", ("String-concatenated / dynamic queries — HIGH risk under "
|
|
513
|
+
"Hibernate 6 HQL parser changes")
|
|
514
|
+
effort = _effort_bucket(len(layer_files[LAYER_HQL]), 0.3)
|
|
515
|
+
else:
|
|
516
|
+
risk, reason = "medium", ("Static HQL / native queries — revalidate against "
|
|
517
|
+
"Hibernate 6 parser; mostly mechanical")
|
|
518
|
+
effort = _effort_bucket(len(layer_files[LAYER_HQL]), 0.15)
|
|
519
|
+
matrix.append(HibernateLayerRisk(
|
|
520
|
+
LAYER_HQL, risk, reason, effort,
|
|
521
|
+
len(layer_files[LAYER_HQL]), layer_occ[LAYER_HQL]))
|
|
522
|
+
|
|
523
|
+
# Layer 4
|
|
524
|
+
if layer_files[LAYER_SPI]:
|
|
525
|
+
risk, reason = "critical", ("Custom Hibernate SPI/internal API (UserType, Interceptor, "
|
|
526
|
+
"EventListener, engine.spi) — primary H5→H6 breakage")
|
|
527
|
+
effort = _effort_bucket(len(layer_files[LAYER_SPI]), 1.0)
|
|
528
|
+
matrix.append(HibernateLayerRisk(
|
|
529
|
+
LAYER_SPI, risk, reason, effort,
|
|
530
|
+
len(layer_files[LAYER_SPI]), layer_occ[LAYER_SPI]))
|
|
531
|
+
|
|
532
|
+
strat.risk_matrix = matrix
|
|
533
|
+
|
|
534
|
+
# ── Module exposure map: serialize sets ────────────────────────────────
|
|
535
|
+
exposure: dict[str, dict] = {}
|
|
536
|
+
for mod_name, info in modules.items():
|
|
537
|
+
exposure[mod_name] = {
|
|
538
|
+
"max_risk": info["max_risk"],
|
|
539
|
+
"criteria_dynamic": info["criteria_dynamic"],
|
|
540
|
+
"has_spi": info["has_spi"],
|
|
541
|
+
"has_reflection": info["has_reflection"],
|
|
542
|
+
"file_count": len(info["files"]),
|
|
543
|
+
"layers": {
|
|
544
|
+
layer: {"severity": d["severity"], "occurrences": d["occurrences"]}
|
|
545
|
+
for layer, d in info["layers"].items()
|
|
546
|
+
},
|
|
547
|
+
}
|
|
548
|
+
strat.module_exposure = exposure
|
|
549
|
+
|
|
550
|
+
# ── Stop-condition logic → upgrade vs rewrite classification ────────────
|
|
551
|
+
stops: list[str] = []
|
|
552
|
+
if criteria_dynamic_global:
|
|
553
|
+
stops.append("Criteria API used dynamically / via abstraction layers "
|
|
554
|
+
"(DynamicEntityDao, GenericDao, reflection)")
|
|
555
|
+
if spi_global:
|
|
556
|
+
stops.append("Custom Hibernate SPI present (UserType / Interceptor / EventListener / "
|
|
557
|
+
"engine.spi internals)")
|
|
558
|
+
if reflection_query_global:
|
|
559
|
+
stops.append("Queries generated via reflection / runtime entity metadata")
|
|
560
|
+
if concat_query_global:
|
|
561
|
+
stops.append("SQL/HQL shape not statically inferable (string concatenation)")
|
|
562
|
+
strat.stop_conditions_triggered = stops
|
|
563
|
+
|
|
564
|
+
if stops:
|
|
565
|
+
strat.classification = CLASS_REWRITE
|
|
566
|
+
elif layer_files[LAYER_CRITERIA] or concat_query_global or jpa_deprecated_seen:
|
|
567
|
+
strat.classification = CLASS_UPGRADE_CARE
|
|
568
|
+
elif layer_files[LAYER_HQL] or layer_files[LAYER_JPA]:
|
|
569
|
+
strat.classification = CLASS_UPGRADE
|
|
570
|
+
else:
|
|
571
|
+
strat.classification = CLASS_UPGRADE
|
|
572
|
+
strat.classification_label = _CLASS_LABELS[strat.classification]
|
|
573
|
+
|
|
574
|
+
# ── Risk separation: observable vs inferred ─────────────────────────────
|
|
575
|
+
observable: list[str] = []
|
|
576
|
+
inferred: list[str] = []
|
|
577
|
+
if layer_files[LAYER_JPA]:
|
|
578
|
+
observable.append(f"JPA mapping annotations in {len(layer_files[LAYER_JPA])} file(s)")
|
|
579
|
+
if layer_files[LAYER_CRITERIA]:
|
|
580
|
+
observable.append(f"Criteria API references in {len(layer_files[LAYER_CRITERIA])} file(s)")
|
|
581
|
+
if layer_files[LAYER_HQL]:
|
|
582
|
+
observable.append(f"HQL/native query calls in {len(layer_files[LAYER_HQL])} file(s)")
|
|
583
|
+
if layer_files[LAYER_SPI]:
|
|
584
|
+
observable.append(f"Hibernate SPI/internal API in {len(layer_files[LAYER_SPI])} file(s)")
|
|
585
|
+
|
|
586
|
+
if criteria_dynamic_global or reflection_query_global:
|
|
587
|
+
inferred.append("Runtime-generated query shapes (reflection/metadata) — exact SQL "
|
|
588
|
+
"cannot be statically enumerated; breakage surface is larger than "
|
|
589
|
+
"observable call sites")
|
|
590
|
+
if spi_global:
|
|
591
|
+
inferred.append("Custom SPI hooks fire at runtime across the persistence lifecycle — "
|
|
592
|
+
"behavioural breakage may not be visible at the call site")
|
|
593
|
+
if concat_query_global:
|
|
594
|
+
inferred.append("Concatenated query strings resolve at runtime — H6 HQL parser changes "
|
|
595
|
+
"may break queries not reachable by static inspection")
|
|
596
|
+
strat.observable_risk = observable
|
|
597
|
+
strat.inferred_runtime_risk = inferred
|
|
598
|
+
|
|
599
|
+
return strat
|
sourcecode/mcp/registry.py
CHANGED
|
@@ -1415,9 +1415,12 @@ Rules detected:
|
|
|
1415
1415
|
MIG-007 medium — javax.inject imports (DI annotations)
|
|
1416
1416
|
MIG-008 medium — javax.ws.rs imports (JAX-RS API)
|
|
1417
1417
|
|
|
1418
|
-
Returns: schema_version, readiness_score (0–100; 100=ready to migrate),
|
|
1419
|
-
|
|
1420
|
-
|
|
1418
|
+
Returns: schema_version, readiness_score (0–100; 100=ready to migrate),
|
|
1419
|
+
jakarta_readiness / boot3_readiness / jdk_modernization (per-dimension 0–100),
|
|
1420
|
+
blocking_count, estimated_effort_days, spring_boot_2_detected (true|false|null —
|
|
1421
|
+
null=undetermined, never assumed true), spring_boot_version_detected,
|
|
1422
|
+
summary (total_findings, affected_files, by_severity, by_rule), findings[],
|
|
1423
|
+
limitations, metadata.
|
|
1421
1424
|
findings fields: id, rule_id, severity, title, source_file, first_line,
|
|
1422
1425
|
imports_found, explanation, fix_hint.
|
|
1423
1426
|
|
sourcecode/mcp/server.py
CHANGED
|
@@ -792,7 +792,10 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
|
|
|
792
792
|
|
|
793
793
|
Maps to: sourcecode migrate-check <repo_path> --min-severity <min_severity>
|
|
794
794
|
Returns: MigrationReport with schema_version, readiness_score (0–100; 100=ready to migrate),
|
|
795
|
-
|
|
795
|
+
jakarta_readiness / boot3_readiness / jdk_modernization (per-dimension 0–100),
|
|
796
|
+
blocking_count, estimated_effort_days,
|
|
797
|
+
spring_boot_2_detected (true|false|null — null=undetermined, never assumed true),
|
|
798
|
+
spring_boot_version_detected,
|
|
796
799
|
summary (total_findings, affected_files, by_severity, by_rule),
|
|
797
800
|
findings[], limitations, metadata.
|
|
798
801
|
findings fields: id, rule_id, severity, title, source_file, first_line,
|
sourcecode/migrate_check.py
CHANGED
|
@@ -6,6 +6,8 @@ that must be addressed when migrating:
|
|
|
6
6
|
- Java 8 → 17 / 21 (SecurityManager, Nashorn, Unsafe, reflection, etc.)
|
|
7
7
|
- XML Spring config (applicationContext.xml, web.xml, security XML)
|
|
8
8
|
- Dependency incompatibilities (SpringFox, Hibernate 5, ByteBuddy old)
|
|
9
|
+
- Hibernate 5→6 stratified migration model (see hibernate_strat) — 4 independent
|
|
10
|
+
layers, module exposure map, call-chain detection, upgrade-vs-rewrite verdict
|
|
9
11
|
|
|
10
12
|
Entry point: run_migrate_check(file_paths, root) → MigrationReport
|
|
11
13
|
"""
|
|
@@ -18,7 +20,10 @@ import re
|
|
|
18
20
|
from dataclasses import dataclass, field
|
|
19
21
|
from datetime import datetime, timezone
|
|
20
22
|
from pathlib import Path
|
|
21
|
-
from typing import Optional
|
|
23
|
+
from typing import Optional, TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from sourcecode.hibernate_strat import HibernateStratification
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
# ---------------------------------------------------------------------------
|
|
@@ -541,6 +546,26 @@ SEVERITY_ORDER: dict[str, int] = {"critical": 0, "high": 1, "medium": 2, "low":
|
|
|
541
546
|
# headline on a repo with zero blockers. See MigrationReport.finalize.
|
|
542
547
|
_LOW_SEVERITY_DEDUCTION_CAP: int = 15
|
|
543
548
|
|
|
549
|
+
# Migration targets that block a Spring Boot 2→3 upgrade (namespace + security).
|
|
550
|
+
# Everything else (java_8_best_practice, java_9_plus, java_11/15/17/18+) is
|
|
551
|
+
# orthogonal JDK modernization debt and must not sink the migration headline.
|
|
552
|
+
_JAKARTA_TARGETS: frozenset[str] = frozenset({"jakarta"})
|
|
553
|
+
_BOOT3_MIGRATION_TARGETS: frozenset[str] = frozenset(
|
|
554
|
+
{"jakarta", "spring_boot_3", "spring_security_6"}
|
|
555
|
+
)
|
|
556
|
+
# Cap on total readiness deduction from JDK-modernization findings (medium/low),
|
|
557
|
+
# so reflection/date cleanups cannot collapse a jakarta-ready repo's headline.
|
|
558
|
+
_JDK_ADVISORY_DEDUCTION_CAP: int = 15
|
|
559
|
+
|
|
560
|
+
# Jakarta EE 9+ namespace imports — strong evidence the repo is already on
|
|
561
|
+
# Boot 3 / Jakarta. Used to veto a false spring_boot_2_detected verdict.
|
|
562
|
+
_JAKARTA_IMPORT_RE: re.Pattern = re.compile(
|
|
563
|
+
r"^[ \t]*import\s+jakarta\.(?:persistence|servlet|validation|annotation|"
|
|
564
|
+
r"transaction|inject|ws\.rs|jms|ejb|mail|websocket|faces|enterprise|batch|"
|
|
565
|
+
r"json|el|security)\b",
|
|
566
|
+
re.MULTILINE,
|
|
567
|
+
)
|
|
568
|
+
|
|
544
569
|
|
|
545
570
|
# ---------------------------------------------------------------------------
|
|
546
571
|
# XML config rules (applied to Spring XML config files)
|
|
@@ -979,22 +1004,72 @@ class MigrationFinding:
|
|
|
979
1004
|
# Report
|
|
980
1005
|
# ---------------------------------------------------------------------------
|
|
981
1006
|
|
|
1007
|
+
def _dimension_score(
|
|
1008
|
+
findings: list["MigrationFinding"],
|
|
1009
|
+
targets: "Optional[frozenset[str]]",
|
|
1010
|
+
) -> int:
|
|
1011
|
+
"""0-100 readiness for one migration dimension.
|
|
1012
|
+
|
|
1013
|
+
targets=None scores the JDK-modernization dimension (every target NOT in the
|
|
1014
|
+
Boot 2→3 migration set). Otherwise scores only findings whose migration_target
|
|
1015
|
+
is in `targets`. Severity-weighted by distinct file (low capped, G-1).
|
|
1016
|
+
"""
|
|
1017
|
+
crit: set[str] = set()
|
|
1018
|
+
high: set[str] = set()
|
|
1019
|
+
med: set[str] = set()
|
|
1020
|
+
low: set[str] = set()
|
|
1021
|
+
for f in findings:
|
|
1022
|
+
if targets is None:
|
|
1023
|
+
if f.migration_target in _BOOT3_MIGRATION_TARGETS:
|
|
1024
|
+
continue
|
|
1025
|
+
elif f.migration_target not in targets:
|
|
1026
|
+
continue
|
|
1027
|
+
if f.severity == "critical":
|
|
1028
|
+
crit.add(f.source_file)
|
|
1029
|
+
elif f.severity == "high":
|
|
1030
|
+
high.add(f.source_file)
|
|
1031
|
+
elif f.severity == "medium":
|
|
1032
|
+
med.add(f.source_file)
|
|
1033
|
+
else:
|
|
1034
|
+
low.add(f.source_file)
|
|
1035
|
+
deduction = (
|
|
1036
|
+
len(crit) * 15
|
|
1037
|
+
+ len(high) * 8
|
|
1038
|
+
+ len(med) * 3
|
|
1039
|
+
+ min(len(low) * 1, _LOW_SEVERITY_DEDUCTION_CAP)
|
|
1040
|
+
)
|
|
1041
|
+
return max(0, 100 - deduction)
|
|
1042
|
+
|
|
1043
|
+
|
|
982
1044
|
@dataclass
|
|
983
1045
|
class MigrationReport:
|
|
984
|
-
schema_version: str = "1.
|
|
1046
|
+
schema_version: str = "1.4"
|
|
985
1047
|
generated_at: str = ""
|
|
986
1048
|
repo_id: str = ""
|
|
987
1049
|
git_head: str = ""
|
|
988
1050
|
|
|
989
1051
|
readiness_score: int = 100
|
|
1052
|
+
# Per-dimension readiness (0-100). javax→jakarta namespace, full Boot 2→3
|
|
1053
|
+
# migration, and orthogonal JDK modernization are scored independently so
|
|
1054
|
+
# JDK debt (java.util.Date, reflection) does not sink a jakarta-ready repo.
|
|
1055
|
+
jakarta_readiness: int = 100
|
|
1056
|
+
boot3_readiness: int = 100
|
|
1057
|
+
jdk_modernization: int = 100
|
|
990
1058
|
blocking_count: int = 0
|
|
991
1059
|
estimated_effort_days: float = 0.0
|
|
992
|
-
|
|
1060
|
+
# Tri-state: True = Boot 2 confirmed, False = Boot 3+ confirmed,
|
|
1061
|
+
# None = could not determine. Absence of evidence is never reported as True.
|
|
1062
|
+
spring_boot_2_detected: Optional[bool] = None
|
|
1063
|
+
spring_boot_version_detected: Optional[str] = None
|
|
993
1064
|
|
|
994
1065
|
findings: list[MigrationFinding] = field(default_factory=list)
|
|
995
1066
|
limitations: list[str] = field(default_factory=list)
|
|
996
1067
|
summary: dict = field(default_factory=dict)
|
|
997
1068
|
metadata: dict = field(default_factory=dict)
|
|
1069
|
+
# Hibernate 5→6 stratified migration model (4 independent layers, module
|
|
1070
|
+
# exposure map, call-chain detection, upgrade-vs-rewrite verdict). Attached
|
|
1071
|
+
# by run_migrate_check; None when not computed.
|
|
1072
|
+
hibernate: Optional["HibernateStratification"] = None
|
|
998
1073
|
|
|
999
1074
|
def finalize(self) -> "MigrationReport":
|
|
1000
1075
|
if not self.generated_at:
|
|
@@ -1027,20 +1102,37 @@ class MigrationReport:
|
|
|
1027
1102
|
else:
|
|
1028
1103
|
low_files.add(f.source_file)
|
|
1029
1104
|
|
|
1030
|
-
#
|
|
1031
|
-
#
|
|
1032
|
-
#
|
|
1033
|
-
#
|
|
1034
|
-
#
|
|
1035
|
-
#
|
|
1105
|
+
# BUG-2: aggregate readiness no longer collapses on raw JDK-modernization
|
|
1106
|
+
# volume. Blockers (critical/high, any target) stay uncapped so a genuinely
|
|
1107
|
+
# blocked repo floors at 0 (shopizer: 301 blocking → 0). Medium/low are
|
|
1108
|
+
# split by target: migration-blocking (jakarta/boot3/security) count in
|
|
1109
|
+
# full (low capped, G-1); orthogonal JDK debt (Date/reflection) is capped
|
|
1110
|
+
# so it cannot sink a jakarta-ready repo (Broadleaf: 144 Date + 25 reflection
|
|
1111
|
+
# no longer force 0/100).
|
|
1112
|
+
mig_med = {f.source_file for f in self.findings
|
|
1113
|
+
if f.severity == "medium" and f.migration_target in _BOOT3_MIGRATION_TARGETS}
|
|
1114
|
+
mig_low = {f.source_file for f in self.findings
|
|
1115
|
+
if f.severity == "low" and f.migration_target in _BOOT3_MIGRATION_TARGETS}
|
|
1116
|
+
jdk_med = {f.source_file for f in self.findings
|
|
1117
|
+
if f.severity == "medium" and f.migration_target not in _BOOT3_MIGRATION_TARGETS}
|
|
1118
|
+
jdk_low = {f.source_file for f in self.findings
|
|
1119
|
+
if f.severity == "low" and f.migration_target not in _BOOT3_MIGRATION_TARGETS}
|
|
1120
|
+
|
|
1036
1121
|
deduction = (
|
|
1037
1122
|
len(critical_files) * 15
|
|
1038
1123
|
+ len(high_files) * 8
|
|
1039
|
-
+ len(
|
|
1040
|
-
+ min(len(
|
|
1124
|
+
+ len(mig_med) * 3
|
|
1125
|
+
+ min(len(mig_low) * 1, _LOW_SEVERITY_DEDUCTION_CAP)
|
|
1126
|
+
+ min(len(jdk_med) * 3 + len(jdk_low) * 1, _JDK_ADVISORY_DEDUCTION_CAP)
|
|
1041
1127
|
)
|
|
1042
1128
|
self.readiness_score = max(0, 100 - deduction)
|
|
1043
1129
|
|
|
1130
|
+
# Per-dimension readiness — independent severity-weighted scores so the
|
|
1131
|
+
# output reveals that jakarta/Boot3 may be complete even with JDK debt.
|
|
1132
|
+
self.jakarta_readiness = _dimension_score(self.findings, _JAKARTA_TARGETS)
|
|
1133
|
+
self.boot3_readiness = _dimension_score(self.findings, _BOOT3_MIGRATION_TARGETS)
|
|
1134
|
+
self.jdk_modernization = _dimension_score(self.findings, None)
|
|
1135
|
+
|
|
1044
1136
|
self.estimated_effort_days = round(
|
|
1045
1137
|
len(critical_files) * 0.5
|
|
1046
1138
|
+ len(high_files) * 0.25
|
|
@@ -1065,11 +1157,16 @@ class MigrationReport:
|
|
|
1065
1157
|
"repo_id": self.repo_id,
|
|
1066
1158
|
"git_head": self.git_head,
|
|
1067
1159
|
"readiness_score": self.readiness_score,
|
|
1160
|
+
"jakarta_readiness": self.jakarta_readiness,
|
|
1161
|
+
"boot3_readiness": self.boot3_readiness,
|
|
1162
|
+
"jdk_modernization": self.jdk_modernization,
|
|
1068
1163
|
"blocking_count": self.blocking_count,
|
|
1069
1164
|
"estimated_effort_days": self.estimated_effort_days,
|
|
1070
1165
|
"spring_boot_2_detected": self.spring_boot_2_detected,
|
|
1166
|
+
"spring_boot_version_detected": self.spring_boot_version_detected,
|
|
1071
1167
|
"summary": self.summary,
|
|
1072
1168
|
"findings": [f.to_dict() for f in self.findings],
|
|
1169
|
+
"hibernate": self.hibernate.to_dict() if self.hibernate is not None else None,
|
|
1073
1170
|
"limitations": self.limitations,
|
|
1074
1171
|
"metadata": self.metadata,
|
|
1075
1172
|
}
|
|
@@ -1078,8 +1175,18 @@ class MigrationReport:
|
|
|
1078
1175
|
min_order = SEVERITY_ORDER.get(min_severity, 3)
|
|
1079
1176
|
visible = [f for f in self.findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
1080
1177
|
|
|
1178
|
+
if self.spring_boot_2_detected is True:
|
|
1179
|
+
_boot = "Boot 2 (migration target)"
|
|
1180
|
+
elif self.spring_boot_2_detected is False:
|
|
1181
|
+
_boot = f"Boot {self.spring_boot_version_detected or '3+'} detected"
|
|
1182
|
+
else:
|
|
1183
|
+
_boot = "unknown"
|
|
1081
1184
|
lines: list[str] = [
|
|
1082
1185
|
f"Migration Readiness: {self.readiness_score}/100",
|
|
1186
|
+
f" jakarta: {self.jakarta_readiness}/100 "
|
|
1187
|
+
f"boot3: {self.boot3_readiness}/100 "
|
|
1188
|
+
f"jdk-modernization: {self.jdk_modernization}/100",
|
|
1189
|
+
f"Spring Boot 2 detected: {_boot}",
|
|
1083
1190
|
f"Blocking issues: {self.blocking_count} "
|
|
1084
1191
|
f"(critical: {self.summary.get('by_severity', {}).get('critical', 0)}, "
|
|
1085
1192
|
f"high: {self.summary.get('by_severity', {}).get('high', 0)})",
|
|
@@ -1088,6 +1195,10 @@ class MigrationReport:
|
|
|
1088
1195
|
"",
|
|
1089
1196
|
]
|
|
1090
1197
|
|
|
1198
|
+
if self.hibernate is not None and self.hibernate.detected:
|
|
1199
|
+
lines.append(self.hibernate.to_text())
|
|
1200
|
+
lines.append("")
|
|
1201
|
+
|
|
1091
1202
|
if not visible:
|
|
1092
1203
|
lines.append("No findings at or above selected severity.")
|
|
1093
1204
|
return "\n".join(lines)
|
|
@@ -1200,6 +1311,7 @@ def run_migrate_check(
|
|
|
1200
1311
|
all_findings: list[MigrationFinding] = []
|
|
1201
1312
|
limitations: list[str] = []
|
|
1202
1313
|
read_errors = 0
|
|
1314
|
+
jakarta_import_count = 0
|
|
1203
1315
|
|
|
1204
1316
|
# ── Java source scan ────────────────────────────────────────────────────
|
|
1205
1317
|
for rel_path in file_paths:
|
|
@@ -1210,6 +1322,9 @@ def run_migrate_check(
|
|
|
1210
1322
|
read_errors += 1
|
|
1211
1323
|
continue
|
|
1212
1324
|
|
|
1325
|
+
# Jakarta EE 9+ namespace adoption signal (vetoes a false Boot-2 verdict).
|
|
1326
|
+
jakarta_import_count += len(_JAKARTA_IMPORT_RE.findall(source))
|
|
1327
|
+
|
|
1213
1328
|
file_findings = _scan_file(source, rel_path, _ALL_RULES)
|
|
1214
1329
|
filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
1215
1330
|
all_findings.extend(filtered)
|
|
@@ -1263,11 +1378,18 @@ def run_migrate_check(
|
|
|
1263
1378
|
|
|
1264
1379
|
limitations.extend(_STATIC_LIMITATIONS)
|
|
1265
1380
|
|
|
1266
|
-
spring_boot_2 =
|
|
1381
|
+
spring_boot_2, spring_boot_version = _detect_spring_boot(root, jakarta_import_count)
|
|
1382
|
+
|
|
1383
|
+
# Hibernate 5→6 stratification (independent of min_severity — it is its own
|
|
1384
|
+
# risk model, not a severity-filtered finding stream).
|
|
1385
|
+
from sourcecode.hibernate_strat import analyze_hibernate
|
|
1386
|
+
hibernate_strat = analyze_hibernate(file_paths, root)
|
|
1267
1387
|
|
|
1268
1388
|
report = MigrationReport(
|
|
1269
1389
|
spring_boot_2_detected=spring_boot_2,
|
|
1390
|
+
spring_boot_version_detected=spring_boot_version,
|
|
1270
1391
|
findings=all_findings,
|
|
1392
|
+
hibernate=hibernate_strat,
|
|
1271
1393
|
limitations=limitations,
|
|
1272
1394
|
metadata={
|
|
1273
1395
|
"java_files_scanned": len(file_paths),
|
|
@@ -1309,36 +1431,105 @@ _STATIC_LIMITATIONS: list[str] = [
|
|
|
1309
1431
|
]
|
|
1310
1432
|
|
|
1311
1433
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1434
|
+
# Spring Boot version captured ONLY in an authoritative context (parent,
|
|
1435
|
+
# managed BOM, an explicit spring-boot* dependency version, a spring.boot.version
|
|
1436
|
+
# property, or the Gradle plugin) — NOT any stray 2.x library version elsewhere
|
|
1437
|
+
# in the pom. These run on property-resolved text so ${spring.boot.version}
|
|
1438
|
+
# is already substituted.
|
|
1439
|
+
_BOOT_VERSION_PATTERNS: tuple[re.Pattern, ...] = (
|
|
1440
|
+
# <spring.boot.version>3.5.14</...> / <spring-boot.version> property declaration
|
|
1441
|
+
re.compile(r"<spring[.\-_]?boot[.\-_]?version>\s*(\d+)\.\d", re.IGNORECASE),
|
|
1442
|
+
# gradle.properties: springBootVersion=3.5.14 / spring.boot.version=2.7.18
|
|
1443
|
+
re.compile(r"spring[.\-_]?boot[.\-_]?version\s*[=:]\s*[\"']?(\d+)\.\d", re.IGNORECASE),
|
|
1444
|
+
# <artifactId>spring-boot-*</artifactId> immediately followed by <version>
|
|
1445
|
+
re.compile(
|
|
1446
|
+
r"<artifactId>\s*spring-boot[\w-]*\s*</artifactId>\s*<version>\s*(\d+)\.\d",
|
|
1447
|
+
re.IGNORECASE,
|
|
1448
|
+
),
|
|
1449
|
+
# <version> immediately followed by <artifactId>spring-boot-*</artifactId>
|
|
1450
|
+
re.compile(
|
|
1451
|
+
r"<version>\s*(\d+)\.\d[^<]*</version>\s*<artifactId>\s*spring-boot[\w-]*\s*</artifactId>",
|
|
1452
|
+
re.IGNORECASE,
|
|
1453
|
+
),
|
|
1454
|
+
# Gradle plugin: id 'org.springframework.boot' version '3.1.0'
|
|
1455
|
+
re.compile(
|
|
1456
|
+
r"org\.springframework\.boot[\"']?[^\n]*?version[^\n]*?[\"'](\d+)\.\d",
|
|
1457
|
+
re.IGNORECASE,
|
|
1458
|
+
),
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
_BOOT_FULL_VERSION_PATTERNS: tuple[re.Pattern, ...] = (
|
|
1462
|
+
re.compile(r"<spring[.\-_]?boot[.\-_]?version>\s*(\d+\.\d[\w.\-]*)", re.IGNORECASE),
|
|
1463
|
+
re.compile(r"spring[.\-_]?boot[.\-_]?version\s*[=:]\s*[\"']?(\d+\.\d[\w.\-]*)", re.IGNORECASE),
|
|
1464
|
+
re.compile(
|
|
1465
|
+
r"<artifactId>\s*spring-boot[\w-]*\s*</artifactId>\s*<version>\s*(\d+\.\d[\w.\-]*)",
|
|
1466
|
+
re.IGNORECASE,
|
|
1467
|
+
),
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1314
1470
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1471
|
+
def _extract_boot_versions(text: str) -> tuple[set[int], Optional[str]]:
|
|
1472
|
+
"""Return (set of detected Spring Boot major versions, first full version string)."""
|
|
1473
|
+
majors: set[int] = set()
|
|
1474
|
+
for pat in _BOOT_VERSION_PATTERNS:
|
|
1475
|
+
for m in pat.finditer(text):
|
|
1476
|
+
try:
|
|
1477
|
+
majors.add(int(m.group(1)))
|
|
1478
|
+
except (ValueError, IndexError):
|
|
1479
|
+
pass
|
|
1480
|
+
full: Optional[str] = None
|
|
1481
|
+
for pat in _BOOT_FULL_VERSION_PATTERNS:
|
|
1482
|
+
m = pat.search(text)
|
|
1483
|
+
if m:
|
|
1484
|
+
full = m.group(1).strip()
|
|
1485
|
+
break
|
|
1486
|
+
return majors, full
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
def _detect_spring_boot(root: Path, jakarta_import_count: int) -> tuple[Optional[bool], Optional[str]]:
|
|
1490
|
+
"""Tri-state Spring Boot 2 detection. Returns (spring_boot_2_detected, version).
|
|
1491
|
+
|
|
1492
|
+
- True → Spring Boot 2.x confirmed by build evidence (migration target).
|
|
1493
|
+
- False → Boot 3+ confirmed, OR jakarta.* namespace already adopted en masse.
|
|
1494
|
+
- None → version could not be determined. Absence of evidence is never True.
|
|
1495
|
+
|
|
1496
|
+
Resolves Maven ${properties} so version-by-property (no starter-parent) works,
|
|
1497
|
+
and detects Boot via parent, managed BOM, spring-boot* dependency, property,
|
|
1498
|
+
or the Gradle plugin. Massive jakarta.* imports veto a Boot-2 verdict.
|
|
1317
1499
|
"""
|
|
1318
|
-
_SB2 = re.compile(
|
|
1319
|
-
# Maven: <parent>...<artifactId>spring-boot-*</artifactId>...<version>2.x
|
|
1320
|
-
r"spring[.\-]?boot.*?<version>\s*2\.\d+|"
|
|
1321
|
-
r"<version>\s*2\.\d+[\.\d]*\s*</version>.*?spring[.\-]?boot|"
|
|
1322
|
-
# Maven properties: <spring.boot.version>2.x or spring-boot.version=2.x
|
|
1323
|
-
r"spring[.\-_]?boot[.\-_]?version\s*[=>\"'\s]+2\.\d+|"
|
|
1324
|
-
# Gradle plugin: id 'org.springframework.boot' version '2.x'
|
|
1325
|
-
r"org\.springframework\.boot[^\n]*?['\"']2\.\d+",
|
|
1326
|
-
re.IGNORECASE | re.DOTALL,
|
|
1327
|
-
)
|
|
1328
|
-
# Candidate build files: root + one level deep (child modules in monorepos)
|
|
1329
1500
|
root_files = [
|
|
1330
1501
|
root / name
|
|
1331
1502
|
for name in ("pom.xml", "build.gradle", "build.gradle.kts", "gradle.properties")
|
|
1332
1503
|
]
|
|
1333
1504
|
child_poms = list(root.glob("*/pom.xml"))
|
|
1334
1505
|
child_gradle = list(root.glob("*/build.gradle")) + list(root.glob("*/build.gradle.kts"))
|
|
1335
|
-
# Limit child scan to 30 files to stay fast on large monorepos
|
|
1506
|
+
# Limit child scan to 30 files to stay fast on large monorepos.
|
|
1336
1507
|
candidates = root_files + (child_poms + child_gradle)[:30]
|
|
1508
|
+
|
|
1509
|
+
majors: set[int] = set()
|
|
1510
|
+
full_version: Optional[str] = None
|
|
1337
1511
|
for candidate in candidates:
|
|
1338
1512
|
try:
|
|
1339
1513
|
text = candidate.read_text(encoding="utf-8", errors="replace")
|
|
1340
|
-
if _SB2.search(text):
|
|
1341
|
-
return True
|
|
1342
1514
|
except OSError:
|
|
1343
|
-
|
|
1344
|
-
|
|
1515
|
+
continue
|
|
1516
|
+
if candidate.name == "pom.xml":
|
|
1517
|
+
text = _resolve_maven_properties(text)
|
|
1518
|
+
file_majors, file_full = _extract_boot_versions(text)
|
|
1519
|
+
majors |= file_majors
|
|
1520
|
+
if full_version is None and file_full is not None:
|
|
1521
|
+
full_version = file_full
|
|
1522
|
+
|
|
1523
|
+
# Boot 3+ explicitly declared → not a Boot 2 repo.
|
|
1524
|
+
if any(m >= 3 for m in majors):
|
|
1525
|
+
return False, full_version
|
|
1526
|
+
# Boot 2 declared. jakarta.* adoption en masse contradicts a literal Boot-2
|
|
1527
|
+
# verdict (already mid/post namespace migration) → report unknown, never True.
|
|
1528
|
+
if 2 in majors:
|
|
1529
|
+
if jakarta_import_count > 0:
|
|
1530
|
+
return None, full_version
|
|
1531
|
+
return True, full_version
|
|
1532
|
+
# No version evidence. jakarta.* present ⇒ Boot 3 (Jakarta EE 9+).
|
|
1533
|
+
if jakarta_import_count > 0:
|
|
1534
|
+
return False, full_version
|
|
1535
|
+
return None, full_version
|
sourcecode/serializer.py
CHANGED
|
@@ -261,6 +261,49 @@ def _compact_git_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
|
|
|
261
261
|
return ctx if ctx else None
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
# Java EE namespaces that were renamed javax.* → jakarta.* in Jakarta EE 9.
|
|
265
|
+
# Only these carry javax→jakarta migration risk. JDK / JSR namespaces that keep
|
|
266
|
+
# the javax.* prefix forever (javax.cache=JSR-107, javax.sql, javax.xml JAXP,
|
|
267
|
+
# javax.naming, javax.crypto, …) are deliberately NOT listed and must not flag.
|
|
268
|
+
_JAKARTA_RENAMED_NAMESPACES: tuple[str, ...] = (
|
|
269
|
+
"javax.servlet", # also covers javax.servlet.jsp
|
|
270
|
+
"javax.persistence",
|
|
271
|
+
"javax.validation",
|
|
272
|
+
"javax.transaction",
|
|
273
|
+
"javax.ws.rs",
|
|
274
|
+
"javax.ejb",
|
|
275
|
+
"javax.jms",
|
|
276
|
+
"javax.mail",
|
|
277
|
+
"javax.xml.bind", # JAXB — moved; plain javax.xml (JAXP) does not
|
|
278
|
+
"javax.xml.ws",
|
|
279
|
+
"javax.xml.soap",
|
|
280
|
+
"javax.activation",
|
|
281
|
+
"javax.faces",
|
|
282
|
+
"javax.enterprise",
|
|
283
|
+
"javax.inject",
|
|
284
|
+
"javax.batch",
|
|
285
|
+
"javax.json",
|
|
286
|
+
"javax.websocket",
|
|
287
|
+
"javax.el",
|
|
288
|
+
"javax.security.enterprise",
|
|
289
|
+
"javax.security.jacc",
|
|
290
|
+
"javax.annotation", # ambiguous: JSR-250 EE subset moved to jakarta
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _is_jakarta_renamed_namespace(nl: str) -> bool:
|
|
295
|
+
"""True only for javax.* namespaces actually renamed to jakarta.* in Jakarta EE 9.
|
|
296
|
+
|
|
297
|
+
Matches exact namespace or a sub-package (e.g. javax.servlet → javax.servlet.http).
|
|
298
|
+
JDK/JSR namespaces that keep javax.* forever (javax.cache, javax.sql, javax.xml
|
|
299
|
+
JAXP, javax.naming, …) return False.
|
|
300
|
+
"""
|
|
301
|
+
for ns in _JAKARTA_RENAMED_NAMESPACES:
|
|
302
|
+
if nl == ns or nl.startswith(ns + "."):
|
|
303
|
+
return True
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
|
|
264
307
|
def _dep_risk_flags(name: str, version: "Optional[str]") -> list[str]:
|
|
265
308
|
"""Static heuristic risk flags for a single dependency. No external lookups."""
|
|
266
309
|
flags: list[str] = []
|
|
@@ -268,7 +311,7 @@ def _dep_risk_flags(name: str, version: "Optional[str]") -> list[str]:
|
|
|
268
311
|
if "spring-boot" in nl or "spring.boot" in nl:
|
|
269
312
|
if version and version.startswith("2."):
|
|
270
313
|
flags.append("spring-boot-2.x-eol")
|
|
271
|
-
if nl
|
|
314
|
+
if _is_jakarta_renamed_namespace(nl):
|
|
272
315
|
flags.append("javax-to-jakarta-migration-risk")
|
|
273
316
|
if "ojdbc" in nl or nl in {"com.oracle.database.jdbc", "oracle.jdbc.driver.oracledriver"}:
|
|
274
317
|
flags.append("oracle-vendor-lock")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=M2ctLJu2Bc_7rapOGaPAxxYG50XM5jz5dbisyWgh-sE,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=liCwQmLgb5vplohy8arjYxs_HOIv5C9MjLh_gY6bc5Q,44115
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=DEwucOPJguLsVtg5cV8mWXNi112l5jmBhv73KGGebVk,24849
|
|
8
8
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
9
9
|
sourcecode/classifier.py,sha256=hKzg-nQ47htqqIUzSGvYxv15cXrA3KgICTwJmdqal0o,8095
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=bEArsMTzwzag8xk-d2AkQk2I3WiZmVyoILRu_PtYBY0,276304
|
|
11
11
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
12
12
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
13
13
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -28,11 +28,12 @@ sourcecode/format_contract.py,sha256=1cTNqwP8geA2hbQoBHUPgX3_vSh3l8guJT_jmgEnFF8
|
|
|
28
28
|
sourcecode/fqn_utils.py,sha256=XLU7zDkNBXz_RZkIUNfpPmp1nekWtqP-fxV92tDV1vg,2158
|
|
29
29
|
sourcecode/git_analyzer.py,sha256=JStxTQXNjBWi_wLdwhsZs9mT-v50cSJIz4Agzn6Kh9I,13362
|
|
30
30
|
sourcecode/graph_analyzer.py,sha256=DHR8fY69oU_Pi4SYaWboX6EoEFrctQKB9dsjpqwGMzw,62403
|
|
31
|
+
sourcecode/hibernate_strat.py,sha256=Q_IJpO2jZEDaFPyp0v9svlcB3LetOu56R8Mv7S2F3aU,26165
|
|
31
32
|
sourcecode/integration_detector.py,sha256=ZJqrGwvZ4ee2JTGhlazKk67aZi173HxkhNpl8Yntpd8,6503
|
|
32
33
|
sourcecode/license.py,sha256=wckiLuiwaE35KMCStUf1gYzleJuFe6qsSyxUQJvit3s,23500
|
|
33
34
|
sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
|
|
34
35
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
35
|
-
sourcecode/migrate_check.py,sha256=
|
|
36
|
+
sourcecode/migrate_check.py,sha256=2f7f2NCrBmIqh4MzAHthaZ4UQTyAgjIkRPV9Fdi4Nic,65166
|
|
36
37
|
sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
|
|
37
38
|
sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
|
|
38
39
|
sourcecode/path_filters.py,sha256=EN1RGZRvLq5EcPgpjYV_IyCKVlAQQn2bbpEisQ5LpGg,3780
|
|
@@ -52,7 +53,7 @@ sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
|
52
53
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
53
54
|
sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs,3503
|
|
54
55
|
sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIxkw,89176
|
|
55
|
-
sourcecode/serializer.py,sha256=
|
|
56
|
+
sourcecode/serializer.py,sha256=6YoGyRqkjtp34ELwgRGCZ1r1b6zu6SqiAWT8ySjs-M8,126516
|
|
56
57
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
57
58
|
sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
|
|
58
59
|
sourcecode/spring_impact.py,sha256=qLwLfItX_o9LU-k_qjhD2hFpTX3PpEQ85TsYTAArzvg,56016
|
|
@@ -88,9 +89,9 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
|
|
|
88
89
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
89
90
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
90
91
|
sourcecode/mcp/orchestrator.py,sha256=kT7IssYNyQXVtDf2Q69qrMSTuyJlJ1Rhkp6_EHqc_38,36520
|
|
91
|
-
sourcecode/mcp/registry.py,sha256=
|
|
92
|
+
sourcecode/mcp/registry.py,sha256=8ot8A9_ccPNUvHgFkUO8lIlbp4XT_UcHR101nc71Gjc,65840
|
|
92
93
|
sourcecode/mcp/runner.py,sha256=-Dp2qPGRkfNTVen6bKh7WtzQqpcEtsrXoiuajvshlKk,2866
|
|
93
|
-
sourcecode/mcp/server.py,sha256=
|
|
94
|
+
sourcecode/mcp/server.py,sha256=AQN076joOHyKoU7HaISpzPIIN-5271DDn5DqbQsnTaI,63324
|
|
94
95
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
95
96
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
96
97
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -102,8 +103,8 @@ sourcecode/telemetry/consent.py,sha256=H5z2Wu63pZqbaKucRPoJQJ0zCo4cke9ZlBrJC-MaW
|
|
|
102
103
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
103
104
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
104
105
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
105
|
-
sourcecode-1.
|
|
106
|
-
sourcecode-1.
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
109
|
-
sourcecode-1.
|
|
106
|
+
sourcecode-1.62.0.dist-info/METADATA,sha256=pj1Wd3yqoYh6bYXMq-w9Z4ZF4bqAAAfyfMo4sWaMkzQ,38941
|
|
107
|
+
sourcecode-1.62.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
108
|
+
sourcecode-1.62.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
109
|
+
sourcecode-1.62.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
110
|
+
sourcecode-1.62.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|