sourcecode 1.64.0__py3-none-any.whl → 1.65.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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.64.0"
3
+ __version__ = "1.65.0"
@@ -253,6 +253,75 @@ def _scan_build_dependency(root: Path) -> tuple[bool, list[str]]:
253
253
  samples.append(snippet)
254
254
  return found, samples
255
255
 
256
+
257
+ # ── BUG #1: effective Hibernate version resolution ─────────────────────────
258
+ # A 5→6 rewrite verdict is only valid when the project is actually on Hibernate 5.
259
+ # Resolve the effective major version from build descriptors, following Maven
260
+ # ${properties} (incl. BOM-managed values declared as <hibernate*.version>) and
261
+ # explicit hibernate-core / hibernate-orm coordinates. When the resolved major is
262
+ # ≥ 6 the 5→6 axis does NOT apply (the migration is already done). When it cannot
263
+ # be resolved the axis degrades to a low-confidence hypothesis (no headline).
264
+ _HIB_VER = r"(\d+\.\d+(?:\.\d+)?(?:[.\-][\w]+)*)" # 6.2.13.Final, 5.6.15, 6.2
265
+ _HIB_PROP_RE = re.compile(
266
+ r"<((?:[\w.\-]*hibernate[\w.\-]*?)\.?version)>\s*" + _HIB_VER + r"\s*</\1>",
267
+ re.IGNORECASE,
268
+ )
269
+ _HIB_COORD_VERSION_RE = re.compile(
270
+ r"<artifactId>\s*hibernate-(?:core|orm)\s*</artifactId>\s*"
271
+ r"(?:<[^>]+>[^<]*</[^>]+>\s*)*?<version>\s*" + _HIB_VER,
272
+ re.IGNORECASE | re.DOTALL,
273
+ )
274
+ _HIB_GRADLE_VERSION_RE = re.compile(
275
+ r"org\.hibernate(?:\.orm)?:hibernate-(?:core|orm):" + _HIB_VER,
276
+ re.IGNORECASE,
277
+ )
278
+
279
+
280
+ def _ver_key(full: str) -> tuple[int, int]:
281
+ parts = full.split(".")
282
+ try:
283
+ major = int(parts[0])
284
+ except (ValueError, IndexError):
285
+ return (0, 0)
286
+ try:
287
+ minor = int(parts[1]) if len(parts) > 1 else 0
288
+ except ValueError:
289
+ minor = 0
290
+ return (major, minor)
291
+
292
+
293
+ def _resolve_hibernate_version(root: Path) -> tuple[Optional[str], Optional[int], str]:
294
+ """Return (full_version_string, major, confidence) for the effective Hibernate.
295
+
296
+ confidence is "high" when a version was resolved from a build descriptor,
297
+ "none" when none could be determined (degrade to hypothesis). The newest
298
+ declared Hibernate wins in a multi-module build.
299
+ """
300
+ import os
301
+ best_full: Optional[str] = None
302
+ for dirpath, dirnames, filenames in os.walk(root):
303
+ dirnames[:] = [d for d in dirnames if d not in _SKIP_BUILD_SCAN_DIRS]
304
+ for fname in filenames:
305
+ if fname not in _BUILD_FILE_NAMES:
306
+ continue
307
+ try:
308
+ text = (Path(dirpath) / fname).read_text(encoding="utf-8", errors="replace")
309
+ except OSError:
310
+ continue
311
+ fulls: list[str] = []
312
+ for m in _HIB_PROP_RE.finditer(text):
313
+ fulls.append(m.group(2))
314
+ for m in _HIB_COORD_VERSION_RE.finditer(text):
315
+ fulls.append(m.group(1))
316
+ for m in _HIB_GRADLE_VERSION_RE.finditer(text):
317
+ fulls.append(m.group(1))
318
+ for full in fulls:
319
+ if best_full is None or _ver_key(full) > _ver_key(best_full):
320
+ best_full = full
321
+ if best_full is None:
322
+ return None, None, "none"
323
+ return best_full, _ver_key(best_full)[0], "high"
324
+
256
325
  # Escalation markers — dynamic / reflection-based persistence construction.
257
326
  _ABSTRACTION_CLASS_RE = re.compile(
258
327
  r"\b(DynamicEntityDao|DynamicEntityDaoImpl|BasicPersistenceModule|"
@@ -406,12 +475,23 @@ class HibernateStratification:
406
475
  # when backed by a build dependency or a parsed org.hibernate import, "none"
407
476
  # when detection was vetoed for lack of evidence.
408
477
  evidence: dict = field(default_factory=dict)
478
+ # BUG #1: effective Hibernate version + whether the 5→6 axis applies at all.
479
+ # When the resolved major is ≥ 6 the migration is already done → not applicable
480
+ # (never a blocker). When unresolved, the axis is a low-confidence hypothesis.
481
+ effective_version: Optional[str] = None
482
+ version_major: Optional[int] = None
483
+ version_confidence: str = "none"
484
+ migration_applicable: bool = True
409
485
 
410
486
  def to_dict(self) -> dict:
411
487
  return {
412
488
  "schema_version": HIBERNATE_SCHEMA_VERSION,
413
489
  "detected": self.detected,
414
490
  "evidence": self.evidence,
491
+ "effective_version": self.effective_version,
492
+ "version_major": self.version_major,
493
+ "version_confidence": self.version_confidence,
494
+ "migration_applicable": self.migration_applicable,
415
495
  "classification": self.classification,
416
496
  "classification_label": self.classification_label,
417
497
  "stratified": True,
@@ -763,6 +843,7 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
763
843
  return strat
764
844
 
765
845
  dep_present, dep_samples = _scan_build_dependency(root)
846
+ eff_version, version_major, version_conf = _resolve_hibernate_version(root)
766
847
 
767
848
  has_evidence = dep_present or any_hibernate_import or any_jpa_import
768
849
  evidence: dict = {
@@ -771,6 +852,9 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
771
852
  "hibernate_import_present": any_hibernate_import,
772
853
  "hibernate_import_sample": hibernate_import_sample,
773
854
  "jpa_import_present": any_jpa_import,
855
+ "effective_version": eff_version,
856
+ "version_major": version_major,
857
+ "version_confidence": version_conf,
774
858
  "confidence": "high" if has_evidence else "none",
775
859
  }
776
860
 
@@ -959,6 +1043,12 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
959
1043
  strat = HibernateStratification()
960
1044
  strat.findings = findings
961
1045
  strat.evidence = evidence
1046
+ strat.effective_version = eff_version
1047
+ strat.version_major = version_major
1048
+ strat.version_confidence = version_conf
1049
+ # BUG #1: the 5→6 axis only applies when NOT already on Hibernate 6+. A
1050
+ # resolved major ≥ 6 means the migration is complete — never a blocker.
1051
+ strat.migration_applicable = not (version_major is not None and version_major >= 6)
962
1052
  strat.rewrite_targets = sorted(
963
1053
  targets, key=lambda t: (t.source_file, t.line_start, t.layer, t.id)
964
1054
  )
@@ -1126,13 +1216,23 @@ def analyze_hibernate(file_paths: list[str], root: Path) -> HibernateStratificat
1126
1216
  stops.append("SQL/HQL shape not statically inferable (string concatenation)")
1127
1217
  strat.stop_conditions_triggered = stops
1128
1218
 
1129
- if stops:
1219
+ if not strat.migration_applicable:
1220
+ # Already on Hibernate 6+ — the 5→6 stratification below is retained as an
1221
+ # informational exposure map, but it is NOT a migration and NOT a blocker.
1222
+ strat.classification = CLASS_NONE
1223
+ strat.classification_label = (
1224
+ f"ALREADY ON HIBERNATE {strat.version_major} "
1225
+ f"({strat.effective_version}) — NO 5→6 MIGRATION PENDING"
1226
+ )
1227
+ strat.stop_conditions_triggered = []
1228
+ elif stops:
1130
1229
  strat.classification = CLASS_REWRITE
1131
1230
  elif layer_files[LAYER_CRITERIA] or concat_query_global or jpa_deprecated_seen:
1132
1231
  strat.classification = CLASS_UPGRADE_CARE
1133
1232
  else:
1134
1233
  strat.classification = CLASS_UPGRADE
1135
- strat.classification_label = _CLASS_LABELS[strat.classification]
1234
+ if strat.migration_applicable:
1235
+ strat.classification_label = _CLASS_LABELS[strat.classification]
1136
1236
 
1137
1237
  # ── Risk separation: observable vs inferred ─────────────────────────────
1138
1238
  observable: list[str] = []
@@ -35,10 +35,28 @@ _ATTR_NAME_RE = re.compile(r'(?:name|value)\s*=\s*"([^"]*)"')
35
35
  _FIRST_LITERAL_RE = re.compile(r'^\s*"([^"]*)"')
36
36
 
37
37
  # token -> (kind, client). Matched as whole-word usage outside imports/comments.
38
+ # Covers Spring AND plain-Java/Jakarta stacks (Quarkus, Micronaut, Keycloak SPI):
39
+ # the detector must not be Spring-centric, or a non-Spring repo with real LDAP /
40
+ # SMTP / HTTP integrations falsely reports "0 integrations".
38
41
  _TOKEN_CLIENTS: "tuple[tuple[str, str, str], ...]" = (
42
+ # Spring HTTP clients
39
43
  ("RestTemplate", "http", "resttemplate"),
40
44
  ("WebClient", "http", "webclient"),
45
+ # Plain-Java / third-party HTTP clients
46
+ ("HttpClient", "http", "jdk-httpclient"), # java.net.http.HttpClient
47
+ ("CloseableHttpClient", "http", "apache-httpclient"),
48
+ ("HttpClients", "http", "apache-httpclient"),
49
+ ("OkHttpClient", "http", "okhttp"),
50
+ # LDAP / directory (Spring + JNDI)
41
51
  ("LdapTemplate", "ldap", "ldaptemplate"),
52
+ ("InitialLdapContext", "ldap", "jndi-ldap"),
53
+ ("InitialDirContext", "ldap", "jndi-ldap"),
54
+ ("LdapContext", "ldap", "jndi-ldap"),
55
+ # Mail / SMTP (JavaMail / Jakarta Mail)
56
+ ("JavaMailSender", "smtp", "spring-mail"),
57
+ ("MimeMessage", "smtp", "javamail"),
58
+ ("Transport", "smtp", "javamail"),
59
+ # JMS / messaging
42
60
  ("JmsTemplate", "jms", "jmstemplate"),
43
61
  ("ActiveMQConnectionFactory", "jms", "activemq"),
44
62
  )
@@ -156,8 +174,21 @@ def detect_integrations(file_paths: "list[str]", root: Path) -> dict:
156
174
  for r in records:
157
175
  by_kind[r["kind"]] = by_kind.get(r["kind"], 0) + 1
158
176
 
177
+ # BUG #9: honest confidence. A zero count means "no detectable client
178
+ # construct was found", NOT "this system has no integrations" — runtime-wired
179
+ # clients (DI, config-driven endpoints, JCA connectors) are invisible to static
180
+ # text matching. Report that explicitly instead of an authoritative "0".
181
+ confidence = "observed" if records else "not_analyzed"
159
182
  return {
160
183
  "integrations": records,
161
184
  "by_kind": {k: by_kind[k] for k in sorted(by_kind)},
162
185
  "count": len(records),
186
+ "confidence": confidence,
187
+ "coverage_note": (
188
+ "Detects HTTP (RestTemplate/WebClient/JDK/Apache/OkHttp), LDAP (Spring "
189
+ "+ JNDI), SMTP (JavaMail), and JMS client constructs by source-text "
190
+ "matching. A count of 0 means no such construct was found, not that the "
191
+ "system has no outbound integrations — runtime/DI-wired clients are not "
192
+ "statically visible."
193
+ ),
163
194
  }
@@ -22,6 +22,8 @@ from datetime import datetime, timezone
22
22
  from pathlib import Path
23
23
  from typing import Optional, TYPE_CHECKING
24
24
 
25
+ from sourcecode.path_filters import is_test_or_fixture_path
26
+
25
27
  if TYPE_CHECKING:
26
28
  from sourcecode.hibernate_strat import HibernateStratification
27
29
 
@@ -582,6 +584,54 @@ def _is_no_migrate_javax(fqn: str) -> bool:
582
584
  """True if a javax FQN belongs to a JDK/permanent namespace (no jakarta move)."""
583
585
  return any(fqn.startswith(p) for p in _JAKARTA_NO_MIGRATE_PREFIXES)
584
586
 
587
+
588
+ # BUG #8: autogenerated source markers — path fragments and the JSR-250 marker.
589
+ _GENERATED_PATH_FRAGMENTS: tuple[str, ...] = (
590
+ "/generated-sources/", "/generated/", "/target/generated",
591
+ "/build/generated", "/.apt_generated/",
592
+ )
593
+
594
+
595
+ def _classify_code_context(finding: "MigrationFinding") -> str:
596
+ """Bucket a finding as main / test / generated for blocking-count segregation."""
597
+ path = finding.source_file
598
+ if is_test_or_fixture_path(path):
599
+ return "test"
600
+ norm = path.replace("\\", "/").lower()
601
+ if any(frag in norm for frag in _GENERATED_PATH_FRAGMENTS):
602
+ return "generated"
603
+ # javax.annotation.Generated is the marker emitted into autogenerated code.
604
+ if finding.rule_id == "MIG-006" and finding.imports_found and all(
605
+ imp.rsplit(".", 1)[-1] == "Generated" for imp in finding.imports_found
606
+ ):
607
+ return "generated"
608
+ return "main"
609
+
610
+
611
+ # BUG #2: Spring presence signal. The Boot3 dimension only applies to repos that
612
+ # actually use Spring — a Quarkus/Micronaut/Helidon/Jakarta-pure repo has no
613
+ # Spring Boot 2→3 axis. Detected from build coordinates or framework imports.
614
+ _SPRING_BUILD_RE: re.Pattern = re.compile(
615
+ r"org\.springframework(?:\.boot)?\b|spring-boot-starter", re.IGNORECASE
616
+ )
617
+ _SPRING_IMPORT_RE: re.Pattern = re.compile(
618
+ r"^[ \t]*import\s+org\.springframework\.", re.MULTILINE
619
+ )
620
+
621
+
622
+ def _detect_spring_present(root: Path, spring_import_seen: bool) -> bool:
623
+ """True when Spring is actually used (build coordinate or org.springframework import)."""
624
+ if spring_import_seen:
625
+ return True
626
+ for abs_path, _rel in _find_build_files(root):
627
+ try:
628
+ text = abs_path.read_text(encoding="utf-8", errors="replace")
629
+ except OSError:
630
+ continue
631
+ if _SPRING_BUILD_RE.search(text):
632
+ return True
633
+ return False
634
+
585
635
  # G-1: cap on total readiness deduction from low-severity (advisory, non-blocking)
586
636
  # findings, so optional modernization cleanups cannot collapse the migration-readiness
587
637
  # headline on a repo with zero blockers. See MigrationReport.finalize.
@@ -594,6 +644,10 @@ _JAKARTA_TARGETS: frozenset[str] = frozenset({"jakarta"})
594
644
  _BOOT3_MIGRATION_TARGETS: frozenset[str] = frozenset(
595
645
  {"jakarta", "spring_boot_3", "spring_security_6"}
596
646
  )
647
+ # BUG #6: best-practice hygiene (java.util.Date → java.time) blocks NO version
648
+ # upgrade. It is advisory only and must never sink a readiness dimension to 0 —
649
+ # reported as a separate hygiene metric, excluded from JDK-modernization scoring.
650
+ _BEST_PRACTICE_TARGETS: frozenset[str] = frozenset({"java_8_best_practice"})
597
651
  # Cap on total readiness deduction from JDK-modernization findings (medium/low),
598
652
  # so reflection/date cleanups cannot collapse a jakarta-ready repo's headline.
599
653
  _JDK_ADVISORY_DEDUCTION_CAP: int = 15
@@ -1013,6 +1067,10 @@ class MigrationFinding:
1013
1067
  fix_hint: str = ""
1014
1068
  migration_target: str = ""
1015
1069
  openrewrite_recipe: Optional[str] = None
1070
+ # BUG #8: where the finding lives — "main" (product), "test" (test/fixture
1071
+ # harness), or "generated" (autogenerated source). Only "main" counts toward
1072
+ # blocking_count / readiness; test+generated are reported in a separate bucket.
1073
+ code_context: str = "main"
1016
1074
 
1017
1075
  @staticmethod
1018
1076
  def make_id(rule_id: str, source_file: str) -> str:
@@ -1031,6 +1089,7 @@ class MigrationFinding:
1031
1089
  "fix_hint": self.fix_hint,
1032
1090
  "migration_target": self.migration_target,
1033
1091
  "auto_fix_available": bool(self.openrewrite_recipe),
1092
+ "code_context": self.code_context,
1034
1093
  }
1035
1094
  if self.imports_found:
1036
1095
  d["imports_found"] = self.imports_found
@@ -1061,8 +1120,12 @@ def _dimension_score(
1061
1120
  low: set[str] = set()
1062
1121
  for f in findings:
1063
1122
  if targets is None:
1123
+ # JDK-modernization axis: everything NOT a Boot 2→3 blocker, EXCEPT
1124
+ # best-practice hygiene (BUG #6) which is advisory and scored separately.
1064
1125
  if f.migration_target in _BOOT3_MIGRATION_TARGETS:
1065
1126
  continue
1127
+ if f.migration_target in _BEST_PRACTICE_TARGETS:
1128
+ continue
1066
1129
  elif f.migration_target not in targets:
1067
1130
  continue
1068
1131
  if f.severity == "critical":
@@ -1113,6 +1176,13 @@ class MigrationReport:
1113
1176
  # None = could not determine. Absence of evidence is never reported as True.
1114
1177
  spring_boot_2_detected: Optional[bool] = None
1115
1178
  spring_boot_version_detected: Optional[str] = None
1179
+ # BUG #2: whether Spring is used at all. The Boot3 dimension is N/A without it.
1180
+ spring_present: bool = True
1181
+ # BUG #6 / #8: findings that do NOT count toward blocking_count or readiness —
1182
+ # best-practice hygiene (java.util.Date…) and test/fixture/generated buckets,
1183
+ # surfaced separately so the headline reflects real product migration risk.
1184
+ hygiene_findings: int = 0
1185
+ non_blocking: dict = field(default_factory=dict)
1116
1186
 
1117
1187
  findings: list[MigrationFinding] = field(default_factory=list)
1118
1188
  limitations: list[str] = field(default_factory=list)
@@ -1138,82 +1208,122 @@ class MigrationReport:
1138
1208
  by_target[f.migration_target] = by_target.get(f.migration_target, 0) + 1
1139
1209
  affected_files.add(f.source_file)
1140
1210
 
1141
- self.blocking_count = by_severity["critical"] + by_severity["high"]
1211
+ # BUG #8: only product ("main") code counts toward blocking_count, the
1212
+ # readiness dimensions, and effort. Test harnesses/fixtures (deprecated
1213
+ # web.xml descriptors, test-only sun.* HTTP servers) and autogenerated
1214
+ # sources are reported in a separate non-blocking bucket — counting them
1215
+ # as product blockers inflates the verdict.
1216
+ main_findings = [f for f in self.findings if f.code_context == "main"]
1217
+
1218
+ def _files_by_sev(items: list["MigrationFinding"], sev: str,
1219
+ targets: "Optional[frozenset[str]]" = None,
1220
+ exclude: "Optional[frozenset[str]]" = None) -> set[str]:
1221
+ out: set[str] = set()
1222
+ for f in items:
1223
+ if f.severity != sev:
1224
+ continue
1225
+ if targets is not None and f.migration_target not in targets:
1226
+ continue
1227
+ if exclude is not None and f.migration_target in exclude:
1228
+ continue
1229
+ out.add(f.source_file)
1230
+ return out
1231
+
1232
+ critical_files = _files_by_sev(main_findings, "critical")
1233
+ high_files = _files_by_sev(main_findings, "high")
1234
+ medium_files = _files_by_sev(main_findings, "medium")
1235
+ low_files = _files_by_sev(main_findings, "low")
1236
+
1237
+ self.blocking_count = sum(
1238
+ 1 for f in main_findings if f.severity in ("critical", "high")
1239
+ )
1142
1240
 
1143
- critical_files: set[str] = set()
1144
- high_files: set[str] = set()
1145
- medium_files: set[str] = set()
1146
- low_files: set[str] = set()
1147
- for f in self.findings:
1148
- if f.severity == "critical":
1149
- critical_files.add(f.source_file)
1150
- elif f.severity == "high":
1151
- high_files.add(f.source_file)
1152
- elif f.severity == "medium":
1153
- medium_files.add(f.source_file)
1154
- else:
1155
- low_files.add(f.source_file)
1156
-
1157
- # BUG-2: aggregate readiness no longer collapses on raw JDK-modernization
1158
- # volume. Blockers (critical/high, any target) stay uncapped so a genuinely
1159
- # blocked repo floors at 0 (shopizer: 301 blocking → 0). Medium/low are
1160
- # split by target: migration-blocking (jakarta/boot3/security) count in
1161
- # full (low capped, G-1); orthogonal JDK debt (Date/reflection) is capped
1162
- # so it cannot sink a jakarta-ready repo (Broadleaf: 144 Date + 25 reflection
1163
- # no longer force 0/100).
1164
- mig_med = {f.source_file for f in self.findings
1165
- if f.severity == "medium" and f.migration_target in _BOOT3_MIGRATION_TARGETS}
1166
- mig_low = {f.source_file for f in self.findings
1167
- if f.severity == "low" and f.migration_target in _BOOT3_MIGRATION_TARGETS}
1168
- jdk_med = {f.source_file for f in self.findings
1169
- if f.severity == "medium" and f.migration_target not in _BOOT3_MIGRATION_TARGETS}
1170
- jdk_low = {f.source_file for f in self.findings
1171
- if f.severity == "low" and f.migration_target not in _BOOT3_MIGRATION_TARGETS}
1241
+ # BUG #4 / #6: readiness deduction. FRAMEWORK blockers (jakarta / Boot3 /
1242
+ # Security critical+high) are UNCAPPED so a genuinely blocked repo floors
1243
+ # at 0. Orthogonal JDK-modernization debt (SecurityManager, sun.*, reflection)
1244
+ # is advisory-CAPPED so it can never sink the framework headline. Best-practice
1245
+ # hygiene (java.util.Date) is EXCLUDED entirely — it blocks no version upgrade.
1246
+ fw = _BOOT3_MIGRATION_TARGETS
1247
+ fw_crit = _files_by_sev(main_findings, "critical", targets=fw)
1248
+ fw_high = _files_by_sev(main_findings, "high", targets=fw)
1249
+ fw_med = _files_by_sev(main_findings, "medium", targets=fw)
1250
+ fw_low = _files_by_sev(main_findings, "low", targets=fw)
1251
+ _jdk_exclude = fw | _BEST_PRACTICE_TARGETS
1252
+ jdk_crit = {f.source_file for f in main_findings
1253
+ if f.severity == "critical" and f.migration_target not in _jdk_exclude}
1254
+ jdk_high = {f.source_file for f in main_findings
1255
+ if f.severity == "high" and f.migration_target not in _jdk_exclude}
1256
+ jdk_med = {f.source_file for f in main_findings
1257
+ if f.severity == "medium" and f.migration_target not in _jdk_exclude}
1258
+ jdk_low = {f.source_file for f in main_findings
1259
+ if f.severity == "low" and f.migration_target not in _jdk_exclude}
1260
+ jdk_raw = len(jdk_crit) * 15 + len(jdk_high) * 8 + len(jdk_med) * 3 + len(jdk_low) * 1
1172
1261
 
1173
1262
  deduction = (
1174
- len(critical_files) * 15
1175
- + len(high_files) * 8
1176
- + len(mig_med) * 3
1177
- + min(len(mig_low) * 1, _LOW_SEVERITY_DEDUCTION_CAP)
1178
- + min(len(jdk_med) * 3 + len(jdk_low) * 1, _JDK_ADVISORY_DEDUCTION_CAP)
1263
+ len(fw_crit) * 15
1264
+ + len(fw_high) * 8
1265
+ + len(fw_med) * 3
1266
+ + min(len(fw_low) * 1, _LOW_SEVERITY_DEDUCTION_CAP)
1267
+ + min(jdk_raw, _JDK_ADVISORY_DEDUCTION_CAP)
1179
1268
  )
1180
1269
  self.readiness_score = max(0, 100 - deduction)
1181
1270
 
1182
- # Per-dimension readiness — independent severity-weighted scores so the
1183
- # output reveals that jakarta/Boot3 may be complete even with JDK debt.
1184
- self.jakarta_readiness = _dimension_score(self.findings, _JAKARTA_TARGETS)
1185
- self.boot3_readiness = _dimension_score(self.findings, _BOOT3_MIGRATION_TARGETS)
1186
- self.jdk_modernization = _dimension_score(self.findings, None)
1187
-
1188
- # Hibernate is its own rewrite axis (orthogonal to jakarta/Boot3); it does
1189
- # NOT sink the headline readiness_score, but is surfaced as a dimension and,
1190
- # in a rewrite zone, as the headline_blocker so 62/100 is not read as "easy".
1191
- _hibernate_applies = self.hibernate is not None and self.hibernate.detected
1271
+ # Per-dimension readiness — independent severity-weighted scores (MAIN only).
1272
+ self.jakarta_readiness = _dimension_score(main_findings, _JAKARTA_TARGETS)
1273
+ self.boot3_readiness = _dimension_score(main_findings, _BOOT3_MIGRATION_TARGETS)
1274
+ self.jdk_modernization = _dimension_score(main_findings, None)
1275
+
1276
+ # BUG #1: Hibernate 5→6 is its own rewrite axis and applies ONLY when the
1277
+ # repo is actually on Hibernate < 6. A resolved Hibernate 6+ (Keycloak:
1278
+ # 6.2.13) means the migration is already done N/A, never a blocker. An
1279
+ # unresolved version degrades to a low-confidence hypothesis (no headline).
1280
+ hib = self.hibernate
1281
+ _hibernate_applies = (
1282
+ hib is not None and hib.detected and hib.migration_applicable
1283
+ )
1284
+ if hib is not None and hib.detected:
1285
+ # Already on H6+ → nothing to migrate → 100; else the rewrite score.
1286
+ self.hibernate_readiness = hib.readiness if hib.migration_applicable else 100
1287
+ if _hibernate_applies and hib.classification == "rewrite_zone" \
1288
+ and hib.version_confidence == "high":
1289
+ # Only a CONFIRMED Hibernate-5 rewrite zone earns the headline blocker.
1290
+ self.headline_blocker = "hibernate_rewrite"
1291
+
1292
+ # BUG #2 / #3: declare which dimensions apply. jakarta + JDK are always
1293
+ # applicable to a Java repo; boot3 applies only when Spring is actually used
1294
+ # (Quarkus/Micronaut/Jakarta-pure repos have no Boot 2→3 axis); hibernate
1295
+ # applies only for a real Hibernate < 6. N/A dimensions carry score=None and
1296
+ # are excluded from the aggregate — never scored as 0.
1297
+ if not self.spring_present:
1298
+ self.boot3_readiness = 100
1192
1299
  if _hibernate_applies:
1193
- self.hibernate_readiness = self.hibernate.readiness
1194
- if self.hibernate.classification == "rewrite_zone":
1195
- self.headline_blocker = "hibernate_rewrite"
1196
-
1197
- # BUG #3: declare which dimensions apply. jakarta/boot3/jdk are always
1198
- # applicable to a Java/Spring repo; hibernate applies only when a real
1199
- # Hibernate dependency/import was found. An inapplicable dimension is N/A
1200
- # (score=None) and is excluded from the aggregate — never scored as 0.
1300
+ _hib_reason = f"Hibernate 5→6 rewrite axis (detected {hib.effective_version or 'version unknown'})"
1301
+ elif hib is not None and hib.detected and not hib.migration_applicable:
1302
+ _hib_reason = (f"N/A — already on Hibernate {hib.version_major} "
1303
+ f"({hib.effective_version}); no 5→6 migration pending")
1304
+ else:
1305
+ _hib_reason = "N/A no Hibernate dependency or import detected"
1201
1306
  self.applicable_dimensions = {
1202
1307
  "jakarta": {"applicable": True, "score": self.jakarta_readiness,
1203
1308
  "reason": "javax→jakarta namespace migration"},
1204
- "boot3": {"applicable": True, "score": self.boot3_readiness,
1205
- "reason": "Spring Boot 2→3 / Security 6 migration"},
1309
+ "boot3": {
1310
+ "applicable": self.spring_present,
1311
+ "score": self.boot3_readiness if self.spring_present else None,
1312
+ "reason": ("Spring Boot 2→3 / Security 6 migration"
1313
+ if self.spring_present
1314
+ else "N/A — no Spring usage detected (non-Spring stack)"),
1315
+ },
1206
1316
  "jdk_modernization": {"applicable": True, "score": self.jdk_modernization,
1207
1317
  "reason": "orthogonal JDK modernization debt"},
1208
1318
  "hibernate": {
1209
1319
  "applicable": _hibernate_applies,
1210
1320
  "score": self.hibernate_readiness if _hibernate_applies else None,
1211
- "reason": ("Hibernate 5→6 rewrite axis"
1212
- if _hibernate_applies
1213
- else "N/A — no Hibernate dependency or import detected"),
1321
+ "reason": _hib_reason,
1214
1322
  },
1215
1323
  }
1216
1324
 
1325
+ # BUG #5: effort over MAIN findings only — N/A axes (Hibernate-6 phantom,
1326
+ # test fixtures) no longer pad the estimate.
1217
1327
  self.estimated_effort_days = round(
1218
1328
  len(critical_files) * 0.5
1219
1329
  + len(high_files) * 0.25
@@ -1222,12 +1332,34 @@ class MigrationReport:
1222
1332
  1,
1223
1333
  )
1224
1334
 
1335
+ # BUG #6 / #8: hygiene + non-blocking buckets, surfaced separately.
1336
+ self.hygiene_findings = sum(
1337
+ 1 for f in main_findings if f.migration_target in _BEST_PRACTICE_TARGETS
1338
+ )
1339
+ _nb_by_ctx: dict[str, int] = {}
1340
+ _nb_by_rule: dict[str, int] = {}
1341
+ for f in self.findings:
1342
+ if f.code_context == "main":
1343
+ continue
1344
+ _nb_by_ctx[f.code_context] = _nb_by_ctx.get(f.code_context, 0) + 1
1345
+ _nb_by_rule[f.rule_id] = _nb_by_rule.get(f.rule_id, 0) + 1
1346
+ self.non_blocking = {
1347
+ "count": sum(_nb_by_ctx.values()),
1348
+ "by_context": _nb_by_ctx,
1349
+ "by_rule": _nb_by_rule,
1350
+ "note": ("Findings in test/fixture harnesses or autogenerated sources. "
1351
+ "Excluded from blocking_count, readiness, and effort."),
1352
+ }
1353
+
1225
1354
  self.summary = {
1226
1355
  "total_findings": len(self.findings),
1227
1356
  "affected_files": len(affected_files),
1228
1357
  "by_severity": by_severity,
1229
1358
  "by_rule": by_rule,
1230
1359
  "by_migration_target": by_target,
1360
+ "main_findings": len(main_findings),
1361
+ "non_blocking_findings": self.non_blocking["count"],
1362
+ "hygiene_findings": self.hygiene_findings,
1231
1363
  }
1232
1364
  return self
1233
1365
 
@@ -1251,6 +1383,9 @@ class MigrationReport:
1251
1383
  "headline_blocker": self.headline_blocker,
1252
1384
  "blocking_count": self.blocking_count,
1253
1385
  "estimated_effort_days": self.estimated_effort_days,
1386
+ "hygiene_findings": self.hygiene_findings,
1387
+ "non_blocking": self.non_blocking,
1388
+ "spring_present": self.spring_present,
1254
1389
  "spring_boot_2_detected": self.spring_boot_2_detected,
1255
1390
  "spring_boot_version_detected": self.spring_boot_version_detected,
1256
1391
  "summary": self.summary,
@@ -1270,19 +1405,31 @@ class MigrationReport:
1270
1405
  _boot = f"Boot {self.spring_boot_version_detected or '3+'} detected"
1271
1406
  else:
1272
1407
  _boot = "unknown"
1408
+
1409
+ def _dim(name: str) -> str:
1410
+ d = self.applicable_dimensions.get(name, {})
1411
+ if not d.get("applicable", True):
1412
+ return f"{name}: N/A"
1413
+ return f"{name}: {d.get('score')}/100"
1414
+
1415
+ # Blocking parenthetical reflects MAIN (product) findings only — matching
1416
+ # blocking_count — so the text headline cannot contradict the JSON.
1417
+ main_crit = sum(1 for f in self.findings
1418
+ if f.code_context == "main" and f.severity == "critical")
1419
+ main_high = sum(1 for f in self.findings
1420
+ if f.code_context == "main" and f.severity == "high")
1421
+ nb = self.non_blocking.get("count", 0)
1273
1422
  lines: list[str] = [
1274
1423
  f"Migration Readiness: {self.readiness_score}/100",
1275
- f" jakarta: {self.jakarta_readiness}/100 "
1276
- f"boot3: {self.boot3_readiness}/100 "
1277
- f"jdk-modernization: {self.jdk_modernization}/100 "
1278
- f"hibernate: {self.hibernate_readiness}/100",
1424
+ f" {_dim('jakarta')} {_dim('boot3')} "
1425
+ f"{_dim('jdk_modernization')} {_dim('hibernate')}",
1279
1426
  *([f" ⚠ Headline blocker: {self.headline_blocker} "
1280
1427
  f"(readiness_score reflects jakarta/Boot3 only — Hibernate is a separate rewrite axis)"]
1281
1428
  if self.headline_blocker else []),
1282
- f"Spring Boot 2 detected: {_boot}",
1283
- f"Blocking issues: {self.blocking_count} "
1284
- f"(critical: {self.summary.get('by_severity', {}).get('critical', 0)}, "
1285
- f"high: {self.summary.get('by_severity', {}).get('high', 0)})",
1429
+ f"Spring present: {self.spring_present} Spring Boot 2 detected: {_boot}",
1430
+ f"Blocking issues (product code): {self.blocking_count} "
1431
+ f"(critical: {main_crit}, high: {main_high})"
1432
+ + (f" [+{nb} in test/generated, non-blocking]" if nb else ""),
1286
1433
  f"Affected files: {self.summary.get('affected_files', 0)}",
1287
1434
  f"Estimated effort: {self.estimated_effort_days}d",
1288
1435
  "",
@@ -1314,6 +1461,42 @@ class MigrationReport:
1314
1461
  # Java source scanner
1315
1462
  # ---------------------------------------------------------------------------
1316
1463
 
1464
+ # BUG #7: javax.annotation.Generated is the JSR-250 marker emitted into
1465
+ # AUTOGENERATED sources (SCIM, JAXB, MapStruct…). It maps mechanically to
1466
+ # jakarta.annotation.Generated and carries low migration value — it must not be
1467
+ # described with the generic "@PostConstruct/@PreDestroy/@Resource" blurb, nor
1468
+ # rated medium alongside real CDI lifecycle annotations.
1469
+ _MIG006_LIFECYCLE = ("PostConstruct", "PreDestroy", "Resource",
1470
+ "ManagedBean", "Priority", "Resources")
1471
+
1472
+
1473
+ def _refine_mig006(matched_imports: list[str]) -> tuple[str, str]:
1474
+ """Return (severity, explanation) tailored to the actual javax.annotation symbols.
1475
+
1476
+ Explanation names the concrete symbols detected, not a fixed list. When the
1477
+ only symbol is `Generated`, severity degrades to low (mechanical, generated
1478
+ code); otherwise the CDI lifecycle annotations keep medium severity.
1479
+ """
1480
+ symbols = [imp.rsplit(".", 1)[-1] for imp in matched_imports]
1481
+ lifecycle = [s for s in symbols if s in _MIG006_LIFECYCLE]
1482
+ only_generated = bool(symbols) and all(s == "Generated" for s in symbols)
1483
+ sym_list = ", ".join(f"@{s}" for s in dict.fromkeys(symbols)) or "javax.annotation"
1484
+ if only_generated:
1485
+ return (
1486
+ "low",
1487
+ "javax.annotation.Generated (JSR-250) maps mechanically to "
1488
+ "jakarta.annotation.Generated. It is emitted into autogenerated sources "
1489
+ "and carries low migration value — re-run the generator on the Jakarta "
1490
+ "toolchain rather than hand-editing.",
1491
+ )
1492
+ affected = ", ".join(f"@{s}" for s in dict.fromkeys(lifecycle)) or sym_list
1493
+ return (
1494
+ "medium",
1495
+ f"jakarta.annotation replaces javax.annotation in Jakarta EE 9+. "
1496
+ f"Detected symbol(s): {sym_list}. {affected} are affected.",
1497
+ )
1498
+
1499
+
1317
1500
  def _scan_file(
1318
1501
  source: str,
1319
1502
  rel_path: str,
@@ -1360,16 +1543,21 @@ def _scan_file(
1360
1543
  first_line = min(candidate_lines)
1361
1544
  all_matches = matched_imports + code_snippets
1362
1545
 
1546
+ severity = rule.severity
1547
+ explanation = rule.explanation
1548
+ if rule.id == "MIG-006" and matched_imports:
1549
+ severity, explanation = _refine_mig006(matched_imports)
1550
+
1363
1551
  findings.append(
1364
1552
  MigrationFinding(
1365
1553
  id=MigrationFinding.make_id(rule.id, rel_path),
1366
1554
  rule_id=rule.id,
1367
- severity=rule.severity,
1555
+ severity=severity,
1368
1556
  title=rule.title,
1369
1557
  source_file=rel_path,
1370
1558
  first_line=first_line,
1371
1559
  imports_found=all_matches,
1372
- explanation=rule.explanation,
1560
+ explanation=explanation,
1373
1561
  fix_hint=rule.fix_hint,
1374
1562
  migration_target=rule.migration_target,
1375
1563
  openrewrite_recipe=rule.openrewrite_recipe,
@@ -1410,6 +1598,7 @@ def run_migrate_check(
1410
1598
  limitations: list[str] = []
1411
1599
  read_errors = 0
1412
1600
  jakarta_import_count = 0
1601
+ spring_import_seen = False
1413
1602
 
1414
1603
  # ── Java source scan ────────────────────────────────────────────────────
1415
1604
  for rel_path in file_paths:
@@ -1422,6 +1611,8 @@ def run_migrate_check(
1422
1611
 
1423
1612
  # Jakarta EE 9+ namespace adoption signal (vetoes a false Boot-2 verdict).
1424
1613
  jakarta_import_count += len(_JAKARTA_IMPORT_RE.findall(source))
1614
+ if not spring_import_seen and _SPRING_IMPORT_RE.search(source):
1615
+ spring_import_seen = True
1425
1616
 
1426
1617
  file_findings = _scan_file(source, rel_path, _ALL_RULES)
1427
1618
  filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
@@ -1477,6 +1668,12 @@ def run_migrate_check(
1477
1668
  limitations.extend(_STATIC_LIMITATIONS)
1478
1669
 
1479
1670
  spring_boot_2, spring_boot_version = _detect_spring_boot(root, jakarta_import_count)
1671
+ spring_present = _detect_spring_present(root, spring_import_seen)
1672
+
1673
+ # BUG #8: classify each finding's code context (main / test / generated) so the
1674
+ # report can keep test fixtures and autogenerated sources out of blocking_count.
1675
+ for f in all_findings:
1676
+ f.code_context = _classify_code_context(f)
1480
1677
 
1481
1678
  # Hibernate 5→6 stratification (independent of min_severity — it is its own
1482
1679
  # risk model, not a severity-filtered finding stream).
@@ -1486,6 +1683,7 @@ def run_migrate_check(
1486
1683
  report = MigrationReport(
1487
1684
  spring_boot_2_detected=spring_boot_2,
1488
1685
  spring_boot_version_detected=spring_boot_version,
1686
+ spring_present=spring_present,
1489
1687
  findings=all_findings,
1490
1688
  hibernate=hibernate_strat,
1491
1689
  limitations=limitations,
@@ -12,6 +12,16 @@ _TEST_SEGMENTS = frozenset({
12
12
  "integrationtest", "integrationtests",
13
13
  })
14
14
 
15
+ # Whole-module directories that are test harness / fixtures even though their
16
+ # code lives under src/main (e.g. a Maven module that ships a test framework).
17
+ # A finding under one of these modules is test infrastructure, not the product.
18
+ _TEST_MODULE_SEGMENTS = frozenset({
19
+ "testsuite", "test-framework", "testframework",
20
+ "integration-arquillian", "arquillian",
21
+ "test-utils", "test-util", "testutils",
22
+ "test-support", "testsupport",
23
+ })
24
+
15
25
  _VENDOR_SEGMENTS = frozenset({
16
26
  "vendor", "vendors",
17
27
  "third_party", "thirdparty",
@@ -86,6 +96,29 @@ def is_test_path(path: str) -> bool:
86
96
  return False
87
97
 
88
98
 
99
+ def is_test_or_fixture_path(path: str) -> bool:
100
+ """Return True when *path* is test code OR test-harness/fixture infrastructure.
101
+
102
+ Broader than is_test_path: also catches whole modules that are test frameworks
103
+ or integration-test harnesses (testsuite/, test-framework/, integration-arquillian/,
104
+ *-test*, *-it modules) even when their sources sit under src/main. Migration
105
+ tooling uses this to keep test fixtures (deprecated web.xml deployment
106
+ descriptors, test-only sun.* HTTP servers) out of the product's blocking count.
107
+ """
108
+ if is_test_path(path):
109
+ return True
110
+ norm = path.replace("\\", "/").lower()
111
+ parts = norm.split("/")
112
+ for part in parts[:-1]: # skip the filename
113
+ bare = part.rstrip("/")
114
+ if bare in _TEST_MODULE_SEGMENTS:
115
+ return True
116
+ # module dirs like "adapter-test", "foo-tests", "bar-itests"
117
+ if bare.endswith(("-test", "-tests", "-it", "-itest", "-itests")):
118
+ return True
119
+ return False
120
+
121
+
89
122
  def is_vendor_path(path: str) -> bool:
90
123
  """Return True when *path* is inside a vendored / third-party directory.
91
124
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.64.0
3
+ Version: 1.65.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -411,7 +411,7 @@ Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the ki
411
411
  |------|--------|
412
412
  | `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
413
413
  | `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
414
- | `--integrations` | Outbound integrations (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `JmsTemplate`, ActiveMQ) with `file:line` evidence and a literal `target` URL/name when present. |
414
+ | `--integrations` | Outbound integrations across Spring **and** plain-Java/Jakarta stacks: HTTP (`RestTemplate`, `WebClient`, `@FeignClient`, JDK `HttpClient`, Apache, OkHttp), LDAP (`LdapTemplate`, JNDI `InitialDirContext`/`LdapContext`), SMTP (`JavaMailSender`, JavaMail `Transport`/`MimeMessage`), and JMS — with `file:line` evidence and a literal `target` when present. Emits an explicit `confidence` (`observed` / `not_analyzed`) + `coverage_note`: a count of `0` means "no client construct found", **not** "no integrations" (runtime/DI-wired clients are not statically visible). |
415
415
  | `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). `components.module_roots` rolls leaf source dirs up to architectural module roots and classifies each `layered` (DDD: ≥2 of `domain`/`application`/`infrastructure`) vs `flat` (legacy/flat package), with a verifiable `module_count` — so a consumer enumerates real modules instead of inferring boundaries from leaf directories. |
416
416
 
417
417
  The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
@@ -610,7 +610,7 @@ Detects migration blockers across Java source files, Spring XML config files, an
610
610
  | `MIG-002` | high | `javax.servlet` import — Servlet API changed |
611
611
  | `MIG-003` | high | `javax.validation` import — Bean Validation changed |
612
612
  | `MIG-004` | high | `javax.transaction` import — TX API changed |
613
- | `MIG-006` | medium | `javax.annotation` import — CDI annotations changed |
613
+ | `MIG-006` | medium¹ | `javax.annotation` import — CDI annotations changed (¹`javax.annotation.Generated` in autogenerated code → **low**, bucketed `generated`; explanation names the actual symbol) |
614
614
  | `MIG-007` | medium | `javax.inject` import — DI annotations changed |
615
615
  | `MIG-008` | medium | `javax.ws.rs` import — JAX-RS changed |
616
616
  | `MIG-009` | medium | `javax.jms` import — JMS API changed |
@@ -722,9 +722,39 @@ reported as **N/A** (`score: null`), never as `0`. The top-level
722
722
  states that `readiness_score` is a derived aggregate over applicable dimensions
723
723
  only — for decisions, read the per-dimension breakdown + `blocking_count`.
724
724
 
725
+ #### Reliability guarantees (no version, no verdict)
726
+
727
+ A migration verdict shown to a decision-maker must never contradict its own
728
+ evidence. `migrate-check` enforces:
729
+
730
+ - **Version-gated framework axes.** The Hibernate 5→6 axis applies only when the
731
+ effective Hibernate version (resolved from `pom.xml`/Gradle, following Maven
732
+ `${properties}` and BOM-managed `<hibernate*.version>`) is `< 6`. A resolved
733
+ Hibernate **6+** repo is reported `migration_applicable: false` (N/A) — no
734
+ `hibernate_rewrite` headline. An **unresolved** version degrades to a
735
+ low-confidence hypothesis with no headline blocker. See
736
+ `hibernate.effective_version` / `version_major` / `version_confidence`.
737
+ - **Boot3 auto-disables without Spring.** `boot3.applicable` tracks real Spring
738
+ usage; Quarkus / Micronaut / Helidon / Jakarta-pure repos report `boot3` as N/A
739
+ (`spring_present: false`), not a contradictory `applicable: true`.
740
+ - **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
741
+ (`javax.xml.*`, `javax.crypto.*`, `javax.naming.*`, `javax.sql.*`,
742
+ `javax.management.*`, `javax.security.auth.*`, …) are allowlisted across every
743
+ jakarta scorer.
744
+ - **Three buckets: blocker ≠ hygiene ≠ test.** Only product (`main`) code counts
745
+ toward `blocking_count`, readiness, and effort. Test harnesses
746
+ (`testsuite/`, `test-framework/`, `integration-arquillian/`, `*-test*`,
747
+ `src/test/`) and autogenerated sources are tagged `code_context` and surfaced in
748
+ `non_blocking{}`. Best-practice hygiene (`java.util.Date`) is reported as
749
+ `hygiene_findings` and never sinks a dimension. Each finding carries
750
+ `code_context: main | test | generated`.
751
+
725
752
  ```bash
726
753
  # inspect only the Hibernate rewrite targets
727
754
  sourcecode migrate-check . --format json | jq '.hibernate.rewrite_targets[]'
755
+
756
+ # product blockers only (excludes test/fixtures + hygiene)
757
+ sourcecode migrate-check . --format json | jq '.blocking_count, .non_blocking, .hygiene_findings'
728
758
  ```
729
759
 
730
760
  ### `rename-class` — Java class rename
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=icvkhugNj_JqAoF4YlHhszmsFC7whNNmc8xxgqsJUcI,103
1
+ sourcecode/__init__.py,sha256=p8-GKiFQV7kVIfsCmKNXmTPDXKUpvscoIAbYoLSteFs,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
@@ -28,15 +28,15 @@ 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=4OKDnfdiJ38_XJIFKBXwZa4ai-Vgb54_FDlo27HgFfE,53147
32
- sourcecode/integration_detector.py,sha256=ZJqrGwvZ4ee2JTGhlazKk67aZi173HxkhNpl8Yntpd8,6503
31
+ sourcecode/hibernate_strat.py,sha256=ZyI0sikxh7IemU8uqn1Z73z-tHeFeCJuYhJnyQL8pzU,57765
32
+ sourcecode/integration_detector.py,sha256=mQulsXN1P-4V_3ueh3xy_N9x3Aqlvm7GQ-3YGqlBEYM,8223
33
33
  sourcecode/license.py,sha256=wckiLuiwaE35KMCStUf1gYzleJuFe6qsSyxUQJvit3s,23500
34
34
  sourcecode/mcp_nudge.py,sha256=5ELU_ixzh6uA83NXLOZT8h00OhL53okfQdji3jyKOjg,2917
35
35
  sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
36
- sourcecode/migrate_check.py,sha256=iHQou8w8hAjSrHmiYpcz2NMyHSatdu-6vbHbyf-kFrs,70268
36
+ sourcecode/migrate_check.py,sha256=pv7jZGhHRaw160sWYk0NWR-jWZERl_2S_gFAJ72maFI,80133
37
37
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
38
38
  sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
39
- sourcecode/path_filters.py,sha256=EN1RGZRvLq5EcPgpjYV_IyCKVlAQQn2bbpEisQ5LpGg,3780
39
+ sourcecode/path_filters.py,sha256=DMea0KIlmj2pzTkuy-3YYFF4ktP0q5zP9mH4v4uXh84,5237
40
40
  sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
41
41
  sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
42
42
  sourcecode/prepare_context.py,sha256=PsRaKeABMh964niCS578WRgcccW9bajnm2yHS9zcKuM,222655
@@ -103,8 +103,8 @@ sourcecode/telemetry/consent.py,sha256=H5z2Wu63pZqbaKucRPoJQJ0zCo4cke9ZlBrJC-MaW
103
103
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
104
104
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
105
105
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
106
- sourcecode-1.64.0.dist-info/METADATA,sha256=qYsbbDRRWaCjq-T81IN2j3-u0i29R6XmvuSHT0s70FE,43969
107
- sourcecode-1.64.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
108
- sourcecode-1.64.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
109
- sourcecode-1.64.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
110
- sourcecode-1.64.0.dist-info/RECORD,,
106
+ sourcecode-1.65.0.dist-info/METADATA,sha256=tuUMAq5_nLV0J0Zrsl8TAS1NdxLSgunoAtnhlFoYv2U,46300
107
+ sourcecode-1.65.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
108
+ sourcecode-1.65.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
109
+ sourcecode-1.65.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
110
+ sourcecode-1.65.0.dist-info/RECORD,,