sourcecode 1.65.0__py3-none-any.whl → 1.67.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 +113 -7
- sourcecode/context_summarizer.py +5 -1
- sourcecode/dependency_analyzer.py +50 -4
- sourcecode/mcp/orchestrator.py +1 -1
- sourcecode/mcp/registry.py +3 -3
- sourcecode/mcp/server.py +4 -4
- sourcecode/migrate_check.py +232 -83
- sourcecode/path_filters.py +31 -0
- sourcecode/serializer.py +54 -6
- sourcecode/summarizer.py +1 -1
- {sourcecode-1.65.0.dist-info → sourcecode-1.67.0.dist-info}/METADATA +16 -4
- {sourcecode-1.65.0.dist-info → sourcecode-1.67.0.dist-info}/RECORD +16 -16
- {sourcecode-1.65.0.dist-info → sourcecode-1.67.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.65.0.dist-info → sourcecode-1.67.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.65.0.dist-info → sourcecode-1.67.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -4896,7 +4896,8 @@ def migrate_check_cmd(
|
|
|
4896
4896
|
output, output_path, copy,
|
|
4897
4897
|
success_msg=(
|
|
4898
4898
|
f"Migration check written to {output_path} "
|
|
4899
|
-
f"(score: {report.readiness_score
|
|
4899
|
+
f"(score: {report.readiness_score if report.readiness_score is not None else 'N/A'}"
|
|
4900
|
+
f"{'/100' if report.readiness_score is not None else ''}, {_total} findings)"
|
|
4900
4901
|
),
|
|
4901
4902
|
)
|
|
4902
4903
|
|
|
@@ -5524,6 +5525,91 @@ def fix_bug_cmd(
|
|
|
5524
5525
|
)
|
|
5525
5526
|
|
|
5526
5527
|
|
|
5528
|
+
# Method signatures that are almost always FRAMEWORK ENTRY POINTS, not dead code —
|
|
5529
|
+
# invoked by a dispatcher (reflection / XML / SPI), invisible to a static Java
|
|
5530
|
+
# call-graph. Generalizes beyond any one framework.
|
|
5531
|
+
_DYNAMIC_ENTRY_SIGNATURE_RE = __import__("re").compile(
|
|
5532
|
+
r"\(\s*DispatchContext\b" # OFBiz Service Engine service
|
|
5533
|
+
r"|HttpServletRequest\s+\w+\s*,\s*HttpServletResponse" # OFBiz event / servlet handler
|
|
5534
|
+
r"|@(?:Scheduled|PostConstruct|PreDestroy|EventListener|Bean|"
|
|
5535
|
+
r"RequestMapping|GetMapping|PostMapping|Path|GET|POST|Provider|"
|
|
5536
|
+
r"ApplicationScoped|Singleton)\b", # annotation-dispatched entry
|
|
5537
|
+
)
|
|
5538
|
+
# Config file extensions where a framework wires classes by name (XML/props/yaml).
|
|
5539
|
+
_CONFIG_REF_EXTS: frozenset = frozenset({".xml", ".properties", ".yml", ".yaml", ".groovy"})
|
|
5540
|
+
_CONFIG_SCAN_MAX_FILES: int = 12000
|
|
5541
|
+
_CONFIG_SCAN_MAX_BYTES: int = 256 * 1024
|
|
5542
|
+
|
|
5543
|
+
|
|
5544
|
+
def _partition_static_unreferenced(nodes: list[dict], root: Path) -> tuple[list[dict], list[dict]]:
|
|
5545
|
+
"""Split zero-degree classes into (truly_unreferenced, framework_dispatched).
|
|
5546
|
+
|
|
5547
|
+
A class with no static callers is NOT necessarily dead: frameworks invoke
|
|
5548
|
+
classes via reflection, XML/SPI config, or annotations that a static call-graph
|
|
5549
|
+
cannot see (e.g. Apache OFBiz Service Engine services, JAX-RS resources,
|
|
5550
|
+
ServiceLoader providers, scheduled beans). We exclude a candidate when EITHER:
|
|
5551
|
+
1. its source declares a dynamic-entry method signature, OR
|
|
5552
|
+
2. its simple name / FQN is referenced from a non-Java config file.
|
|
5553
|
+
Whatever survives is reported as *statically_unreferenced* — never a confident
|
|
5554
|
+
"dead zone".
|
|
5555
|
+
"""
|
|
5556
|
+
import os
|
|
5557
|
+
if not nodes:
|
|
5558
|
+
return [], []
|
|
5559
|
+
by_simple: dict[str, list[dict]] = {}
|
|
5560
|
+
for n in nodes:
|
|
5561
|
+
simple = (n.get("fqn") or "").rsplit(".", 1)[-1]
|
|
5562
|
+
if simple:
|
|
5563
|
+
by_simple.setdefault(simple, []).append(n)
|
|
5564
|
+
|
|
5565
|
+
dispatched_fqns: set[str] = set()
|
|
5566
|
+
|
|
5567
|
+
# 1. Source-signature allowlist (bounded — candidate set is small).
|
|
5568
|
+
for n in nodes:
|
|
5569
|
+
src = n.get("source_file")
|
|
5570
|
+
if not src:
|
|
5571
|
+
continue
|
|
5572
|
+
try:
|
|
5573
|
+
txt = (root / src).read_text(encoding="utf-8", errors="replace")
|
|
5574
|
+
except OSError:
|
|
5575
|
+
continue
|
|
5576
|
+
if _DYNAMIC_ENTRY_SIGNATURE_RE.search(txt):
|
|
5577
|
+
dispatched_fqns.add(n["fqn"])
|
|
5578
|
+
|
|
5579
|
+
# 2. Config-reference scan — find candidate names wired from XML/props/yaml.
|
|
5580
|
+
unresolved_simple = {s for s, ns in by_simple.items()
|
|
5581
|
+
if any(x["fqn"] not in dispatched_fqns for x in ns)}
|
|
5582
|
+
if unresolved_simple:
|
|
5583
|
+
files_scanned = 0
|
|
5584
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
5585
|
+
dirnames[:] = [d for d in dirnames
|
|
5586
|
+
if d not in {".git", "build", "out", "target", "node_modules", ".gradle"}]
|
|
5587
|
+
for fname in filenames:
|
|
5588
|
+
ext = os.path.splitext(fname)[1].lower()
|
|
5589
|
+
if ext not in _CONFIG_REF_EXTS:
|
|
5590
|
+
continue
|
|
5591
|
+
if files_scanned >= _CONFIG_SCAN_MAX_FILES or not unresolved_simple:
|
|
5592
|
+
break
|
|
5593
|
+
fpath = os.path.join(dirpath, fname)
|
|
5594
|
+
try:
|
|
5595
|
+
with open(fpath, "r", encoding="utf-8", errors="replace") as fh:
|
|
5596
|
+
text = fh.read(_CONFIG_SCAN_MAX_BYTES)
|
|
5597
|
+
except OSError:
|
|
5598
|
+
continue
|
|
5599
|
+
files_scanned += 1
|
|
5600
|
+
for simple in list(unresolved_simple):
|
|
5601
|
+
if simple in text:
|
|
5602
|
+
for x in by_simple.get(simple, []):
|
|
5603
|
+
dispatched_fqns.add(x["fqn"])
|
|
5604
|
+
unresolved_simple.discard(simple)
|
|
5605
|
+
if files_scanned >= _CONFIG_SCAN_MAX_FILES or not unresolved_simple:
|
|
5606
|
+
break
|
|
5607
|
+
|
|
5608
|
+
unreferenced = [n for n in nodes if n["fqn"] not in dispatched_fqns]
|
|
5609
|
+
dispatched = [n for n in nodes if n["fqn"] in dispatched_fqns]
|
|
5610
|
+
return unreferenced, dispatched
|
|
5611
|
+
|
|
5612
|
+
|
|
5527
5613
|
@app.command("modernize")
|
|
5528
5614
|
def modernize_cmd(
|
|
5529
5615
|
path: Path = typer.Argument(
|
|
@@ -5625,13 +5711,21 @@ def modernize_cmd(
|
|
|
5625
5711
|
key=lambda n: (-n.get("in_degree", 0), n.get("fqn", "")),
|
|
5626
5712
|
)[:20]
|
|
5627
5713
|
|
|
5628
|
-
#
|
|
5629
|
-
|
|
5714
|
+
# Statically-unreferenced zones: classes with zero in-degree AND zero out-degree
|
|
5715
|
+
# in the Java call-graph. These are NOT necessarily dead — framework dispatch
|
|
5716
|
+
# (reflection / XML / SPI / annotations) is invisible to a static graph — so we
|
|
5717
|
+
# partition out framework-dispatched entry points before reporting, and never
|
|
5718
|
+
# call the survivors "dead". (Defect 5: OFBiz Service-Engine services and event
|
|
5719
|
+
# handlers were false-positive "dead zones".)
|
|
5720
|
+
_zero_degree = sorted(
|
|
5630
5721
|
[n for n in graph_nodes
|
|
5631
5722
|
if n.get("in_degree", 0) == 0 and n.get("out_degree", 0) == 0
|
|
5632
5723
|
and n.get("type") in ("class", "interface")],
|
|
5633
5724
|
key=lambda n: n.get("fqn", ""),
|
|
5634
|
-
)
|
|
5725
|
+
)
|
|
5726
|
+
dead_zones, framework_dispatched = _partition_static_unreferenced(_zero_degree, root)
|
|
5727
|
+
dead_zones = dead_zones[:20]
|
|
5728
|
+
framework_dispatched = framework_dispatched[:20]
|
|
5635
5729
|
|
|
5636
5730
|
# Hotspot candidates: high in-degree service/repository/controller nodes,
|
|
5637
5731
|
# ranked by composite score (in_degree × 2 + git_churn) for volatility signal.
|
|
@@ -5680,7 +5774,8 @@ def modernize_cmd(
|
|
|
5680
5774
|
"total_classes": len([n for n in graph_nodes if n.get("type") in ("class", "interface")]),
|
|
5681
5775
|
"total_subsystems": len(subsystems),
|
|
5682
5776
|
"high_coupling_nodes": len(coupling_nodes),
|
|
5683
|
-
"
|
|
5777
|
+
"statically_unreferenced": len(dead_zones),
|
|
5778
|
+
"framework_dispatched": len(framework_dispatched),
|
|
5684
5779
|
}
|
|
5685
5780
|
_subsystem_summary = [
|
|
5686
5781
|
{
|
|
@@ -5723,10 +5818,20 @@ def modernize_cmd(
|
|
|
5723
5818
|
{"fqn": n["fqn"], "in_degree": n.get("in_degree", 0), "role": n.get("role", "other")}
|
|
5724
5819
|
for n in coupling_nodes
|
|
5725
5820
|
],
|
|
5726
|
-
"
|
|
5821
|
+
"statically_unreferenced": [
|
|
5727
5822
|
{"fqn": n["fqn"], "type": n.get("type", ""), "role": n.get("role", "other")}
|
|
5728
5823
|
for n in dead_zones
|
|
5729
5824
|
],
|
|
5825
|
+
"statically_unreferenced_note": (
|
|
5826
|
+
"Zero static callers in the Java call-graph. NOT confirmed dead: verify "
|
|
5827
|
+
"no framework dispatch (reflection, XML/SPI config, annotations) before "
|
|
5828
|
+
"removing. Classes detected as framework-dispatched are listed separately "
|
|
5829
|
+
"under framework_dispatched and excluded from this list."
|
|
5830
|
+
),
|
|
5831
|
+
"framework_dispatched": [
|
|
5832
|
+
{"fqn": n["fqn"], "type": n.get("type", ""), "role": n.get("role", "other")}
|
|
5833
|
+
for n in framework_dispatched
|
|
5834
|
+
],
|
|
5730
5835
|
"subsystem_summary": _subsystem_summary,
|
|
5731
5836
|
"cross_module_tangles": [
|
|
5732
5837
|
{
|
|
@@ -5742,7 +5847,8 @@ def modernize_cmd(
|
|
|
5742
5847
|
if hotspots else
|
|
5743
5848
|
"high_coupling_nodes shows the most-referenced classes — start there. "
|
|
5744
5849
|
)
|
|
5745
|
-
+ "
|
|
5850
|
+
+ "statically_unreferenced lists classes with no Java callers — review "
|
|
5851
|
+
+ "for framework dispatch (XML/reflection/SPI) before removing. "
|
|
5746
5852
|
+ "Cross-module tangles indicate coupling worth decomposing."
|
|
5747
5853
|
),
|
|
5748
5854
|
}
|
sourcecode/context_summarizer.py
CHANGED
|
@@ -28,7 +28,11 @@ _STACK_LABELS: dict[str, str] = {
|
|
|
28
28
|
|
|
29
29
|
_TYPE_LABELS: dict[str, str] = {
|
|
30
30
|
"api": "REST API",
|
|
31
|
-
|
|
31
|
+
# Vendor-neutral: web_mvc is triggered by server-side template engines
|
|
32
|
+
# (Thymeleaf/FreeMarker), which are NOT exclusive to Spring (e.g. Apache OFBiz
|
|
33
|
+
# uses FreeMarker with its own framework). The detected frameworks list carries
|
|
34
|
+
# the actual stack; the type label must not assert "Spring".
|
|
35
|
+
"web_mvc": "Server-side MVC web app",
|
|
32
36
|
"webapp": "Web app",
|
|
33
37
|
"fullstack": "Full-stack app",
|
|
34
38
|
"cli": "CLI tool",
|
|
@@ -1278,9 +1278,42 @@ class DependencyAnalyzer:
|
|
|
1278
1278
|
return records, limitations
|
|
1279
1279
|
|
|
1280
1280
|
def _strip_gradle_comments(self, content: str) -> str:
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1281
|
+
"""Strip // and /* */ comments WITHOUT touching string literals.
|
|
1282
|
+
|
|
1283
|
+
A naive regex treats `/*` inside a string as a block-comment open. Gradle
|
|
1284
|
+
build files routinely embed `/*` in Ant-style glob strings (e.g.
|
|
1285
|
+
'**/*.java') and `//` in URL strings — a regex stripper then eats the entire
|
|
1286
|
+
dependencies block up to the next literal `*/` (real defect on Apache OFBiz,
|
|
1287
|
+
where ~100 deps vanished). This scanner skips quoted strings (single, double,
|
|
1288
|
+
and triple-quoted, with backslash escapes) so only genuine comments are removed.
|
|
1289
|
+
"""
|
|
1290
|
+
out: list[str] = []
|
|
1291
|
+
i, n = 0, len(content)
|
|
1292
|
+
quote: Optional[str] = None # active string delimiter
|
|
1293
|
+
while i < n:
|
|
1294
|
+
if quote is not None:
|
|
1295
|
+
# Inside a string literal — copy verbatim until the matching delimiter.
|
|
1296
|
+
if len(quote) == 1 and content[i] == "\\" and i + 1 < n:
|
|
1297
|
+
out.append(content[i:i + 2]); i += 2; continue
|
|
1298
|
+
if content.startswith(quote, i):
|
|
1299
|
+
out.append(quote); i += len(quote); quote = None; continue
|
|
1300
|
+
out.append(content[i]); i += 1; continue
|
|
1301
|
+
if content.startswith("//", i):
|
|
1302
|
+
j = content.find("\n", i)
|
|
1303
|
+
if j == -1:
|
|
1304
|
+
break
|
|
1305
|
+
i = j; continue
|
|
1306
|
+
if content.startswith("/*", i):
|
|
1307
|
+
j = content.find("*/", i + 2)
|
|
1308
|
+
if j == -1:
|
|
1309
|
+
break
|
|
1310
|
+
i = j + 2; continue
|
|
1311
|
+
for q in ('"""', "'''", '"', "'"):
|
|
1312
|
+
if content.startswith(q, i):
|
|
1313
|
+
quote = q; out.append(q); i += len(q); break
|
|
1314
|
+
else:
|
|
1315
|
+
out.append(content[i]); i += 1
|
|
1316
|
+
return "".join(out)
|
|
1284
1317
|
|
|
1285
1318
|
def _analyze_gradle(self, root: Path) -> tuple[list[DependencyRecord], list[str]]:
|
|
1286
1319
|
for filename in ("build.gradle", "build.gradle.kts"):
|
|
@@ -1307,7 +1340,20 @@ class DependencyAnalyzer:
|
|
|
1307
1340
|
except OSError:
|
|
1308
1341
|
pass
|
|
1309
1342
|
|
|
1310
|
-
|
|
1343
|
+
deduped = self._dedupe(records)
|
|
1344
|
+
gradle_limitations = [
|
|
1345
|
+
"gradle: no compatible lockfile found; transitive dependencies unavailable"
|
|
1346
|
+
]
|
|
1347
|
+
# Honest-zero guard: if the build file declares a dependencies block but
|
|
1348
|
+
# nothing was resolved, report a GAP rather than a confident "0 deps".
|
|
1349
|
+
if not deduped and re.search(r"\bdependencies\s*\{", content):
|
|
1350
|
+
jar_count = sum(1 for _ in root.glob("lib/**/*.jar"))
|
|
1351
|
+
gap = ("gradle: dependencies declared but none resolved by static "
|
|
1352
|
+
"parsing (non-standard declaration syntax)")
|
|
1353
|
+
if jar_count:
|
|
1354
|
+
gap += f"; {jar_count} jar(s) present under lib/ (not parsed)"
|
|
1355
|
+
gradle_limitations.append(gap)
|
|
1356
|
+
return deduped, gradle_limitations
|
|
1311
1357
|
return [], []
|
|
1312
1358
|
|
|
1313
1359
|
def _parse_gradle_properties(self, root: Path, content: str) -> dict[str, str]:
|
sourcecode/mcp/orchestrator.py
CHANGED
|
@@ -774,7 +774,7 @@ def run_migrate_flow_impl(repo_path: str, min_severity: str = "low") -> dict[str
|
|
|
774
774
|
if not _is_java_repo(repo_path):
|
|
775
775
|
quality_warnings.append(
|
|
776
776
|
"not_a_java_repo: migration analysis targets Spring Boot / Java repos. "
|
|
777
|
-
"Result will be empty (readiness_score=
|
|
777
|
+
"Result will be empty (readiness_score=null/N/A, no findings)."
|
|
778
778
|
)
|
|
779
779
|
|
|
780
780
|
report = _exec(["migrate-check", repo_path, "--min-severity", min_severity])
|
sourcecode/mcp/registry.py
CHANGED
|
@@ -798,7 +798,7 @@ depth: BFS depth for indirect caller traversal (1–8, default: 4).
|
|
|
798
798
|
Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
799
799
|
|
|
800
800
|
Maps to: sourcecode modernize <repo_path>
|
|
801
|
-
Returns: hotspot_candidates (high fan-in + git churn),
|
|
801
|
+
Returns: hotspot_candidates (high fan-in + git churn), statically_unreferenced (zero-caller classes — NOT confirmed dead; verify no framework dispatch) + framework_dispatched,
|
|
802
802
|
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
803
803
|
|
|
804
804
|
Best for: refactor planning, identifying where to start, finding safe removal candidates.
|
|
@@ -1403,7 +1403,7 @@ Spring Boot 2→3 migration readiness: javax→jakarta namespace blockers. JAVA
|
|
|
1403
1403
|
When to call: when asked about Spring Boot migration readiness, javax vs jakarta imports,
|
|
1404
1404
|
or upgrading from Spring Boot 2.x to 3.x. Use BEFORE get_spring_audit when the goal
|
|
1405
1405
|
is migration planning rather than ongoing Spring semantic audit.
|
|
1406
|
-
Do NOT call on non-Java repositories — returns readiness_score=
|
|
1406
|
+
Do NOT call on non-Java repositories — returns readiness_score=null (N/A) with no findings.
|
|
1407
1407
|
|
|
1408
1408
|
Rules detected:
|
|
1409
1409
|
MIG-001 critical — javax.persistence imports (JPA; will not compile after migration)
|
|
@@ -1415,7 +1415,7 @@ 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),
|
|
1418
|
+
Returns: schema_version, readiness_score (0–100, or null=N/A when no migration target applies; 100=ready to migrate),
|
|
1419
1419
|
jakarta_readiness / boot3_readiness / jdk_modernization (per-dimension 0–100),
|
|
1420
1420
|
blocking_count, estimated_effort_days, spring_boot_2_detected (true|false|null —
|
|
1421
1421
|
null=undetermined, never assumed true), spring_boot_version_detected,
|
sourcecode/mcp/server.py
CHANGED
|
@@ -517,7 +517,7 @@ def run_migrate_flow(repo_path: str = ".", min_severity: str = "low") -> dict:
|
|
|
517
517
|
Primary high-value entry point for migration planning. Wraps migrate-check and
|
|
518
518
|
lifts the headline numbers to the top level so the agent can plan a 2→3 upgrade
|
|
519
519
|
without parsing the full report:
|
|
520
|
-
- readiness_score (0–100; 100 = ready), blocking_count, estimated_effort_days
|
|
520
|
+
- readiness_score (0–100, or null=N/A when no migration target applies; 100 = ready), blocking_count, estimated_effort_days
|
|
521
521
|
- by_severity and by_target breakdown (jakarta / spring_security_6 / java_11)
|
|
522
522
|
|
|
523
523
|
Use this instead of calling get_migration_readiness and interpreting it by hand.
|
|
@@ -788,10 +788,10 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
|
|
|
788
788
|
When to call: when asked about Spring Boot migration readiness, javax vs jakarta imports,
|
|
789
789
|
or upgrading from Spring Boot 2.x to 3.x. Call this BEFORE get_spring_audit when
|
|
790
790
|
the goal is migration planning — not ongoing audit.
|
|
791
|
-
Do NOT call on non-Java repositories — returns readiness_score=
|
|
791
|
+
Do NOT call on non-Java repositories — returns readiness_score=null (N/A) with no findings.
|
|
792
792
|
|
|
793
793
|
Maps to: sourcecode migrate-check <repo_path> --min-severity <min_severity>
|
|
794
|
-
Returns: MigrationReport with schema_version, readiness_score (0–100; 100=ready to migrate),
|
|
794
|
+
Returns: MigrationReport with schema_version, readiness_score (0–100, or null=N/A when no migration target applies; 100=ready to migrate),
|
|
795
795
|
jakarta_readiness / boot3_readiness / jdk_modernization / hibernate_readiness
|
|
796
796
|
(per-dimension 0–100), headline_blocker (e.g. "hibernate_rewrite" or null),
|
|
797
797
|
hibernate (Hibernate 5→6 stratified model: 4-layer risk_matrix, rewrite_targets[]
|
|
@@ -1335,7 +1335,7 @@ def modernize_context(repo_path: str = ".", format: str = "json") -> dict:
|
|
|
1335
1335
|
"""Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
1336
1336
|
|
|
1337
1337
|
Maps to: sourcecode modernize <repo_path>
|
|
1338
|
-
Returns: hotspot_candidates (high fan-in + git churn),
|
|
1338
|
+
Returns: hotspot_candidates (high fan-in + git churn), statically_unreferenced (zero-caller classes — NOT confirmed dead; verify no framework dispatch) + framework_dispatched,
|
|
1339
1339
|
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
1340
1340
|
|
|
1341
1341
|
Best for: refactor planning, identifying where to start, finding safe removal candidates.
|
sourcecode/migrate_check.py
CHANGED
|
@@ -608,29 +608,79 @@ def _classify_code_context(finding: "MigrationFinding") -> str:
|
|
|
608
608
|
return "main"
|
|
609
609
|
|
|
610
610
|
|
|
611
|
-
# BUG #2: Spring presence
|
|
612
|
-
#
|
|
613
|
-
#
|
|
614
|
-
|
|
615
|
-
|
|
611
|
+
# BUG #1/#2: Spring presence must be SCOPE- and ARTIFACT-aware. The Boot 2→3 axis
|
|
612
|
+
# only applies to repos that use Spring AT RUNTIME — a Quarkus/Micronaut/Jakarta-pure
|
|
613
|
+
# repo (or one like Apache OFBiz whose ONLY Spring coordinate is spring-test, a TEST
|
|
614
|
+
# support library declared under a legacy `compile` block) has no Spring Boot axis.
|
|
615
|
+
# We therefore split Spring usage into runtime vs test-only and NEVER let a test
|
|
616
|
+
# artifact poison spring_present (which gates the boot3 readiness dimension).
|
|
617
|
+
|
|
618
|
+
# Test-only Spring artifacts: their presence — even when declared in a compile/
|
|
619
|
+
# implementation block — never implies Spring at runtime. The ARTIFACT itself is a
|
|
620
|
+
# test library, regardless of the declared scope.
|
|
621
|
+
_SPRING_TEST_ARTIFACT_RE: re.Pattern = re.compile(
|
|
622
|
+
r"\bspring-(?:test|boot-test(?:-autoconfigure)?|boot-starter-test|security-test)\b",
|
|
623
|
+
re.IGNORECASE,
|
|
616
624
|
)
|
|
625
|
+
# Any `spring-<artifact>` coordinate token (matches both Gradle `org.springframework:
|
|
626
|
+
# spring-core:…` strings and Maven `<artifactId>spring-core</artifactId>` text).
|
|
627
|
+
_SPRING_ARTIFACT_TOKEN_RE: re.Pattern = re.compile(
|
|
628
|
+
r"\bspring-[a-z][a-z0-9]*(?:-[a-z0-9]+)*\b", re.IGNORECASE
|
|
629
|
+
)
|
|
630
|
+
# Spring Boot Gradle plugin / BOM group — an unambiguous runtime signal.
|
|
631
|
+
_SPRING_BOOT_PLUGIN_RE: re.Pattern = re.compile(
|
|
632
|
+
r"""(?:id\s*['"]\s*org\.springframework\.boot|"""
|
|
633
|
+
r"""org\.springframework\.boot\s*[:'"])""",
|
|
634
|
+
re.IGNORECASE,
|
|
635
|
+
)
|
|
636
|
+
# Import-side split: a Spring import in a TEST package (org.springframework.*.test
|
|
637
|
+
# or org.springframework.test / .boot.test) is a test-only signal; any other
|
|
638
|
+
# org.springframework import in MAIN sources is a runtime signal.
|
|
617
639
|
_SPRING_IMPORT_RE: re.Pattern = re.compile(
|
|
618
|
-
r"^[ \t]*import\s+org\.springframework\.", re.MULTILINE
|
|
640
|
+
r"^[ \t]*import\s+(?:static\s+)?org\.springframework\.([\w.]+)", re.MULTILINE
|
|
619
641
|
)
|
|
620
642
|
|
|
621
643
|
|
|
622
|
-
def
|
|
623
|
-
"""
|
|
624
|
-
|
|
625
|
-
|
|
644
|
+
def _build_text_spring_signals(text: str) -> tuple[bool, bool]:
|
|
645
|
+
"""(runtime, any_spring) for one build-file's text — artifact/scope aware.
|
|
646
|
+
|
|
647
|
+
A `spring-*` artifact token that is not a test library, or the Spring Boot
|
|
648
|
+
plugin/BOM group, counts as runtime. spring-test (and friends) alone count as
|
|
649
|
+
"spring present but test-only" — never runtime.
|
|
650
|
+
"""
|
|
651
|
+
runtime = False
|
|
652
|
+
any_spring = False
|
|
653
|
+
if _SPRING_BOOT_PLUGIN_RE.search(text):
|
|
654
|
+
runtime = True
|
|
655
|
+
any_spring = True
|
|
656
|
+
for m in _SPRING_ARTIFACT_TOKEN_RE.finditer(text):
|
|
657
|
+
any_spring = True
|
|
658
|
+
if not _SPRING_TEST_ARTIFACT_RE.match(m.group(0)):
|
|
659
|
+
runtime = True
|
|
660
|
+
return runtime, any_spring
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _detect_spring_usage(
|
|
664
|
+
root: Path, runtime_import_seen: bool, test_import_seen: bool
|
|
665
|
+
) -> tuple[bool, bool]:
|
|
666
|
+
"""Return (runtime_present, test_only).
|
|
667
|
+
|
|
668
|
+
runtime_present — Spring is on the runtime/compile path (build coordinate or a
|
|
669
|
+
MAIN-source org.springframework import that is not a test pkg).
|
|
670
|
+
test_only — Spring appears ONLY as a test dependency (e.g. spring-test);
|
|
671
|
+
the Boot 2→3 migration axis is N/A.
|
|
672
|
+
"""
|
|
673
|
+
runtime = runtime_import_seen
|
|
674
|
+
any_spring = runtime_import_seen or test_import_seen
|
|
626
675
|
for abs_path, _rel in _find_build_files(root):
|
|
627
676
|
try:
|
|
628
677
|
text = abs_path.read_text(encoding="utf-8", errors="replace")
|
|
629
678
|
except OSError:
|
|
630
679
|
continue
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
680
|
+
r, s = _build_text_spring_signals(text)
|
|
681
|
+
runtime = runtime or r
|
|
682
|
+
any_spring = any_spring or s
|
|
683
|
+
return runtime, (any_spring and not runtime)
|
|
634
684
|
|
|
635
685
|
# G-1: cap on total readiness deduction from low-severity (advisory, non-blocking)
|
|
636
686
|
# findings, so optional modernization cleanups cannot collapse the migration-readiness
|
|
@@ -648,6 +698,19 @@ _BOOT3_MIGRATION_TARGETS: frozenset[str] = frozenset(
|
|
|
648
698
|
# upgrade. It is advisory only and must never sink a readiness dimension to 0 —
|
|
649
699
|
# reported as a separate hygiene metric, excluded from JDK-modernization scoring.
|
|
650
700
|
_BEST_PRACTICE_TARGETS: frozenset[str] = frozenset({"java_8_best_practice"})
|
|
701
|
+
|
|
702
|
+
# BUG #3: the migration dimensions that feed the readiness_score aggregate, in
|
|
703
|
+
# order. jdk_modernization is deliberately NOT here — it is orthogonal upkeep debt
|
|
704
|
+
# reported on its own axis, never folded into the migration headline.
|
|
705
|
+
_MIGRATION_DIMENSIONS: tuple[str, ...] = ("jakarta", "boot3", "hibernate")
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _parse_major(version: "Optional[str]") -> "Optional[int]":
|
|
709
|
+
"""Leading integer of a version string ('4.0.3' → 4); None when not parseable."""
|
|
710
|
+
if not version:
|
|
711
|
+
return None
|
|
712
|
+
m = re.match(r"\s*(\d+)", version)
|
|
713
|
+
return int(m.group(1)) if m else None
|
|
651
714
|
# Cap on total readiness deduction from JDK-modernization findings (medium/low),
|
|
652
715
|
# so reflection/date cleanups cannot collapse a jakarta-ready repo's headline.
|
|
653
716
|
_JDK_ADVISORY_DEDUCTION_CAP: int = 15
|
|
@@ -1152,7 +1215,9 @@ class MigrationReport:
|
|
|
1152
1215
|
repo_id: str = ""
|
|
1153
1216
|
git_head: str = ""
|
|
1154
1217
|
|
|
1155
|
-
|
|
1218
|
+
# Optional[int]: None == N/A (no applicable migration dimension), never a
|
|
1219
|
+
# manufactured 100 on a repo with nothing to migrate.
|
|
1220
|
+
readiness_score: Optional[int] = 100
|
|
1156
1221
|
# Per-dimension readiness (0-100). javax→jakarta namespace, full Boot 2→3
|
|
1157
1222
|
# migration, and orthogonal JDK modernization are scored independently so
|
|
1158
1223
|
# JDK debt (java.util.Date, reflection) does not sink a jakarta-ready repo.
|
|
@@ -1170,14 +1235,25 @@ class MigrationReport:
|
|
|
1170
1235
|
# is excluded from the aggregate — it is never counted as 0. Maps dimension →
|
|
1171
1236
|
# {"applicable": bool, "score": int|None, "reason": str}.
|
|
1172
1237
|
applicable_dimensions: dict = field(default_factory=dict)
|
|
1238
|
+
# BUG #3: how readiness_score was derived (method + the exact applicable
|
|
1239
|
+
# dimension scores it aggregates) so the headline number is fully traceable.
|
|
1240
|
+
readiness_aggregate: dict = field(default_factory=dict)
|
|
1173
1241
|
blocking_count: int = 0
|
|
1174
1242
|
estimated_effort_days: float = 0.0
|
|
1175
1243
|
# Tri-state: True = Boot 2 confirmed, False = Boot 3+ confirmed,
|
|
1176
1244
|
# None = could not determine. Absence of evidence is never reported as True.
|
|
1177
1245
|
spring_boot_2_detected: Optional[bool] = None
|
|
1178
1246
|
spring_boot_version_detected: Optional[str] = None
|
|
1179
|
-
# BUG #2: whether Spring is used
|
|
1247
|
+
# BUG #2: whether Spring is used AT RUNTIME. The Boot3 dimension is N/A without it.
|
|
1180
1248
|
spring_present: bool = True
|
|
1249
|
+
# BUG #1/#2: Spring appears ONLY as a test dependency (e.g. spring-test). Reported
|
|
1250
|
+
# so a consumer can see WHY boot3 is N/A despite an org.springframework coordinate.
|
|
1251
|
+
spring_test_only: bool = False
|
|
1252
|
+
# BUG #3/#4: the repo imports the jakarta.* namespace (already on Jakarta EE 9+).
|
|
1253
|
+
# Evidence that the namespace axis is RELEVANT (and complete) — distinguishes an
|
|
1254
|
+
# already-migrated Jakarta repo (jakarta applicable, 100) from a repo that was
|
|
1255
|
+
# never Java EE at all (jakarta N/A).
|
|
1256
|
+
jakarta_namespace_adopted: bool = False
|
|
1181
1257
|
# BUG #6 / #8: findings that do NOT count toward blocking_count or readiness —
|
|
1182
1258
|
# best-practice hygiene (java.util.Date…) and test/fixture/generated buckets,
|
|
1183
1259
|
# surfaced separately so the headline reflects real product migration risk.
|
|
@@ -1238,90 +1314,142 @@ class MigrationReport:
|
|
|
1238
1314
|
1 for f in main_findings if f.severity in ("critical", "high")
|
|
1239
1315
|
)
|
|
1240
1316
|
|
|
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
|
|
1261
|
-
|
|
1262
|
-
deduction = (
|
|
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)
|
|
1268
|
-
)
|
|
1269
|
-
self.readiness_score = max(0, 100 - deduction)
|
|
1270
|
-
|
|
1271
1317
|
# Per-dimension readiness — independent severity-weighted scores (MAIN only).
|
|
1272
1318
|
self.jakarta_readiness = _dimension_score(main_findings, _JAKARTA_TARGETS)
|
|
1273
1319
|
self.boot3_readiness = _dimension_score(main_findings, _BOOT3_MIGRATION_TARGETS)
|
|
1274
1320
|
self.jdk_modernization = _dimension_score(main_findings, None)
|
|
1321
|
+
if not self.spring_present:
|
|
1322
|
+
self.boot3_readiness = 100
|
|
1275
1323
|
|
|
1276
|
-
# BUG #
|
|
1277
|
-
#
|
|
1278
|
-
#
|
|
1279
|
-
#
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1324
|
+
# BUG #3/#4: the jakarta dimension is applicable ONLY with POSITIVE evidence the
|
|
1325
|
+
# namespace axis is relevant — either migratable javax.* code (a javax→jakarta
|
|
1326
|
+
# finding, axis in-progress) OR the jakarta.* namespace already adopted (axis
|
|
1327
|
+
# complete → 100). A repo with neither was never Java EE: jakarta is N/A, never a
|
|
1328
|
+
# manufactured 100 folded into the headline.
|
|
1329
|
+
_jakarta_applies = (
|
|
1330
|
+
any(f.migration_target in _JAKARTA_TARGETS for f in main_findings)
|
|
1331
|
+
or self.jakarta_namespace_adopted
|
|
1283
1332
|
)
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1333
|
+
|
|
1334
|
+
# ── BUG #2: Hibernate applicability — version-driven, never heuristic ────
|
|
1335
|
+
# The 5→6 axis is applicable ONLY when the repo is actually on Hibernate < 6.
|
|
1336
|
+
# resolved < 6 → applicable (rewrite score is a measurement)
|
|
1337
|
+
# resolved ≥ 6 → N/A (already migrated)
|
|
1338
|
+
# unresolved, Boot≥3 → N/A (Boot 3/4 BOM manages Hibernate ORM ≥6)
|
|
1339
|
+
# unresolved, Boot==2 → applicable (Boot 2 BOM manages Hibernate 5.x)
|
|
1340
|
+
# unresolved, no BOM → N/A, status "unresolved" (NEVER a heuristic number
|
|
1341
|
+
# that looks like a measurement on absent data)
|
|
1342
|
+
hib = self.hibernate
|
|
1343
|
+
hib_detected = hib is not None and hib.detected
|
|
1344
|
+
boot_major = _parse_major(self.spring_boot_version_detected)
|
|
1345
|
+
_hibernate_applies = False
|
|
1346
|
+
_hib_score: Optional[int] = None
|
|
1347
|
+
hib_status = "not_detected"
|
|
1348
|
+
_hib_reason = "N/A — no Hibernate dependency or import detected"
|
|
1349
|
+
if hib_detected:
|
|
1350
|
+
if hib.version_confidence == "high":
|
|
1351
|
+
if hib.migration_applicable: # resolved major < 6
|
|
1352
|
+
_hibernate_applies, _hib_score = True, hib.readiness
|
|
1353
|
+
hib_status = "resolved_h5"
|
|
1354
|
+
_hib_reason = f"Hibernate 5→6 rewrite axis (resolved {hib.effective_version})"
|
|
1355
|
+
else: # resolved major ≥ 6
|
|
1356
|
+
hib_status = "managed_ge6"
|
|
1357
|
+
_hib_reason = (f"N/A — resolved Hibernate {hib.effective_version} (≥6); "
|
|
1358
|
+
f"no 5→6 migration pending")
|
|
1359
|
+
elif boot_major is not None and boot_major >= 3:
|
|
1360
|
+
hib_status = "managed_ge6"
|
|
1361
|
+
_hib_reason = (f"N/A — Hibernate version managed by Spring Boot "
|
|
1362
|
+
f"{self.spring_boot_version_detected} BOM (Hibernate ORM ≥6); "
|
|
1363
|
+
f"5→6 axis inapplicable")
|
|
1364
|
+
elif boot_major == 2:
|
|
1365
|
+
_hibernate_applies, _hib_score = True, hib.readiness
|
|
1366
|
+
hib_status = "managed_h5"
|
|
1367
|
+
_hib_reason = (f"Hibernate 5→6 rewrite axis (Hibernate 5.x managed by Spring "
|
|
1368
|
+
f"Boot {self.spring_boot_version_detected} BOM)")
|
|
1369
|
+
else:
|
|
1370
|
+
hib_status = "unresolved"
|
|
1371
|
+
_hib_reason = ("N/A — Hibernate version unresolved (not declared and no Spring "
|
|
1372
|
+
"Boot BOM to infer from); not penalized on absent data")
|
|
1373
|
+
# Top-level scalar: the rewrite score only when applicable; else 100 (nothing
|
|
1374
|
+
# pending) — never a heuristic penalty on an inapplicable/unresolved axis.
|
|
1375
|
+
self.hibernate_readiness = _hib_score if _hibernate_applies else 100
|
|
1376
|
+
# Headline blocker only on a DIRECTLY-resolved Hibernate-5 rewrite zone.
|
|
1377
|
+
if _hibernate_applies and hib is not None and hib.classification == "rewrite_zone" \
|
|
1288
1378
|
and hib.version_confidence == "high":
|
|
1289
|
-
# Only a CONFIRMED Hibernate-5 rewrite zone earns the headline blocker.
|
|
1290
1379
|
self.headline_blocker = "hibernate_rewrite"
|
|
1291
1380
|
|
|
1292
|
-
# BUG #
|
|
1293
|
-
#
|
|
1294
|
-
#
|
|
1295
|
-
#
|
|
1296
|
-
#
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
if _hibernate_applies:
|
|
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"
|
|
1381
|
+
# BUG #3: declare which dimensions apply, then derive readiness_score as a
|
|
1382
|
+
# DOCUMENTED aggregate over the applicable MIGRATION dimensions only. jakarta
|
|
1383
|
+
# is always applicable; boot3 only with Spring; hibernate only for a real
|
|
1384
|
+
# Hibernate < 6. jdk_modernization is an orthogonal upkeep axis (SecurityManager,
|
|
1385
|
+
# reflection, java.time) — reported but EXCLUDED from the headline so JDK debt
|
|
1386
|
+
# cannot sink a framework-complete repo. N/A dimensions carry score=None and
|
|
1387
|
+
# never enter the aggregate.
|
|
1306
1388
|
self.applicable_dimensions = {
|
|
1307
|
-
"jakarta": {
|
|
1308
|
-
|
|
1389
|
+
"jakarta": {
|
|
1390
|
+
"applicable": _jakarta_applies,
|
|
1391
|
+
"score": self.jakarta_readiness if _jakarta_applies else None,
|
|
1392
|
+
"reason": ("javax→jakarta namespace migration"
|
|
1393
|
+
if _jakarta_applies
|
|
1394
|
+
else "N/A — no migratable javax.* imports detected"),
|
|
1395
|
+
},
|
|
1309
1396
|
"boot3": {
|
|
1310
1397
|
"applicable": self.spring_present,
|
|
1311
1398
|
"score": self.boot3_readiness if self.spring_present else None,
|
|
1312
1399
|
"reason": ("Spring Boot 2→3 / Security 6 migration"
|
|
1313
1400
|
if self.spring_present
|
|
1314
|
-
else "N/A —
|
|
1401
|
+
else ("N/A — Spring present only as a TEST dependency "
|
|
1402
|
+
"(spring-test); no runtime Spring"
|
|
1403
|
+
if self.spring_test_only
|
|
1404
|
+
else "N/A — no Spring usage detected (non-Spring stack)")),
|
|
1315
1405
|
},
|
|
1316
1406
|
"jdk_modernization": {"applicable": True, "score": self.jdk_modernization,
|
|
1317
|
-
"reason": "orthogonal JDK modernization debt"
|
|
1407
|
+
"reason": "orthogonal JDK modernization debt (excluded from "
|
|
1408
|
+
"the readiness_score aggregate)",
|
|
1409
|
+
"in_aggregate": False},
|
|
1318
1410
|
"hibernate": {
|
|
1319
1411
|
"applicable": _hibernate_applies,
|
|
1320
1412
|
"score": self.hibernate_readiness if _hibernate_applies else None,
|
|
1321
1413
|
"reason": _hib_reason,
|
|
1414
|
+
"status": hib_status,
|
|
1322
1415
|
},
|
|
1323
1416
|
}
|
|
1324
1417
|
|
|
1418
|
+
# ── BUG #3: aggregate invariant ─────────────────────────────────────────
|
|
1419
|
+
# readiness_score == min(score of every applicable migration dimension).
|
|
1420
|
+
# MIN (not mean): a migration is only as ready as its weakest applicable
|
|
1421
|
+
# axis. _MIGRATION_DIMENSIONS lists exactly which dimensions feed it; the
|
|
1422
|
+
# consistency invariant is asserted in finalize and covered by a unit test.
|
|
1423
|
+
agg_inputs = {
|
|
1424
|
+
name: self.applicable_dimensions[name]["score"]
|
|
1425
|
+
for name in _MIGRATION_DIMENSIONS
|
|
1426
|
+
if self.applicable_dimensions[name]["applicable"]
|
|
1427
|
+
}
|
|
1428
|
+
# BUG #4: when NO migration dimension applies (non-Spring repo, no migratable
|
|
1429
|
+
# javax.*, no Hibernate 5), readiness is N/A (None) — NOT a manufactured 100.
|
|
1430
|
+
# Absence of a migration target is "not applicable", never "100% ready".
|
|
1431
|
+
self.readiness_score = min(agg_inputs.values()) if agg_inputs else None
|
|
1432
|
+
self.readiness_aggregate = {
|
|
1433
|
+
"method": "min",
|
|
1434
|
+
"inputs": agg_inputs,
|
|
1435
|
+
"excluded": ["jdk_modernization"],
|
|
1436
|
+
"applicable": bool(agg_inputs),
|
|
1437
|
+
"note": ("readiness_score = min over applicable migration dimensions "
|
|
1438
|
+
"(jakarta / boot3 / hibernate). jdk_modernization is an orthogonal "
|
|
1439
|
+
"upkeep axis and is intentionally excluded."
|
|
1440
|
+
if agg_inputs else
|
|
1441
|
+
"N/A — no migration target detected (no migratable javax.*, no "
|
|
1442
|
+
"runtime Spring, no Hibernate 5). JDK-modernization findings, if "
|
|
1443
|
+
"any, are reported on their own axis."),
|
|
1444
|
+
}
|
|
1445
|
+
# Internal consistency guard — the headline cannot diverge from the dimensions
|
|
1446
|
+
# it claims to summarize (catches a future scorer change that breaks the model).
|
|
1447
|
+
if agg_inputs:
|
|
1448
|
+
assert self.readiness_score == min(agg_inputs.values()), (
|
|
1449
|
+
"readiness_score must equal min(applicable migration dimensions); "
|
|
1450
|
+
f"got {self.readiness_score} vs inputs {agg_inputs}"
|
|
1451
|
+
)
|
|
1452
|
+
|
|
1325
1453
|
# BUG #5: effort over MAIN findings only — N/A axes (Hibernate-6 phantom,
|
|
1326
1454
|
# test fixtures) no longer pad the estimate.
|
|
1327
1455
|
self.estimated_effort_days = round(
|
|
@@ -1375,10 +1503,13 @@ class MigrationReport:
|
|
|
1375
1503
|
"jdk_modernization": self.jdk_modernization,
|
|
1376
1504
|
"hibernate_readiness": self.hibernate_readiness,
|
|
1377
1505
|
"applicable_dimensions": self.applicable_dimensions,
|
|
1506
|
+
"readiness_aggregate": self.readiness_aggregate,
|
|
1378
1507
|
"readiness_note": (
|
|
1379
|
-
"readiness_score
|
|
1380
|
-
"(
|
|
1381
|
-
"
|
|
1508
|
+
"readiness_score = min over applicable MIGRATION dimensions "
|
|
1509
|
+
"(jakarta / boot3 / hibernate); see readiness_aggregate for the exact "
|
|
1510
|
+
"inputs. N/A dimensions are excluded (never counted as 0); "
|
|
1511
|
+
"jdk_modernization is orthogonal upkeep and is NOT in the aggregate. "
|
|
1512
|
+
"For decisions read the per-dimension breakdown + blocking_count."
|
|
1382
1513
|
),
|
|
1383
1514
|
"headline_blocker": self.headline_blocker,
|
|
1384
1515
|
"blocking_count": self.blocking_count,
|
|
@@ -1386,6 +1517,7 @@ class MigrationReport:
|
|
|
1386
1517
|
"hygiene_findings": self.hygiene_findings,
|
|
1387
1518
|
"non_blocking": self.non_blocking,
|
|
1388
1519
|
"spring_present": self.spring_present,
|
|
1520
|
+
"spring_test_only": self.spring_test_only,
|
|
1389
1521
|
"spring_boot_2_detected": self.spring_boot_2_detected,
|
|
1390
1522
|
"spring_boot_version_detected": self.spring_boot_version_detected,
|
|
1391
1523
|
"summary": self.summary,
|
|
@@ -1419,8 +1551,10 @@ class MigrationReport:
|
|
|
1419
1551
|
main_high = sum(1 for f in self.findings
|
|
1420
1552
|
if f.code_context == "main" and f.severity == "high")
|
|
1421
1553
|
nb = self.non_blocking.get("count", 0)
|
|
1554
|
+
_headline = (f"{self.readiness_score}/100" if self.readiness_score is not None
|
|
1555
|
+
else "N/A (no migration target detected)")
|
|
1422
1556
|
lines: list[str] = [
|
|
1423
|
-
f"Migration Readiness: {
|
|
1557
|
+
f"Migration Readiness: {_headline}",
|
|
1424
1558
|
f" {_dim('jakarta')} {_dim('boot3')} "
|
|
1425
1559
|
f"{_dim('jdk_modernization')} {_dim('hibernate')}",
|
|
1426
1560
|
*([f" ⚠ Headline blocker: {self.headline_blocker} "
|
|
@@ -1598,7 +1732,12 @@ def run_migrate_check(
|
|
|
1598
1732
|
limitations: list[str] = []
|
|
1599
1733
|
read_errors = 0
|
|
1600
1734
|
jakarta_import_count = 0
|
|
1601
|
-
|
|
1735
|
+
# BUG #1/#2: split Spring imports into runtime vs test. A test-tree file, or a
|
|
1736
|
+
# test-only Spring package (org.springframework.test / .boot.test), is a
|
|
1737
|
+
# test-only signal and must NOT mark the repo as runtime-Spring.
|
|
1738
|
+
spring_runtime_import_seen = False
|
|
1739
|
+
spring_test_import_seen = False
|
|
1740
|
+
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
1602
1741
|
|
|
1603
1742
|
# ── Java source scan ────────────────────────────────────────────────────
|
|
1604
1743
|
for rel_path in file_paths:
|
|
@@ -1611,8 +1750,14 @@ def run_migrate_check(
|
|
|
1611
1750
|
|
|
1612
1751
|
# Jakarta EE 9+ namespace adoption signal (vetoes a false Boot-2 verdict).
|
|
1613
1752
|
jakarta_import_count += len(_JAKARTA_IMPORT_RE.findall(source))
|
|
1614
|
-
|
|
1615
|
-
|
|
1753
|
+
_in_test = _is_test_path(rel_path)
|
|
1754
|
+
for _m in _SPRING_IMPORT_RE.finditer(source):
|
|
1755
|
+
_sub = _m.group(1)
|
|
1756
|
+
_is_test_pkg = _sub.startswith(("test.", "boot.test.")) or _sub in ("test", "boot.test")
|
|
1757
|
+
if _in_test or _is_test_pkg:
|
|
1758
|
+
spring_test_import_seen = True
|
|
1759
|
+
else:
|
|
1760
|
+
spring_runtime_import_seen = True
|
|
1616
1761
|
|
|
1617
1762
|
file_findings = _scan_file(source, rel_path, _ALL_RULES)
|
|
1618
1763
|
filtered = [f for f in file_findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
@@ -1668,7 +1813,9 @@ def run_migrate_check(
|
|
|
1668
1813
|
limitations.extend(_STATIC_LIMITATIONS)
|
|
1669
1814
|
|
|
1670
1815
|
spring_boot_2, spring_boot_version = _detect_spring_boot(root, jakarta_import_count)
|
|
1671
|
-
spring_present =
|
|
1816
|
+
spring_present, spring_test_only = _detect_spring_usage(
|
|
1817
|
+
root, spring_runtime_import_seen, spring_test_import_seen
|
|
1818
|
+
)
|
|
1672
1819
|
|
|
1673
1820
|
# BUG #8: classify each finding's code context (main / test / generated) so the
|
|
1674
1821
|
# report can keep test fixtures and autogenerated sources out of blocking_count.
|
|
@@ -1684,6 +1831,8 @@ def run_migrate_check(
|
|
|
1684
1831
|
spring_boot_2_detected=spring_boot_2,
|
|
1685
1832
|
spring_boot_version_detected=spring_boot_version,
|
|
1686
1833
|
spring_present=spring_present,
|
|
1834
|
+
spring_test_only=spring_test_only,
|
|
1835
|
+
jakarta_namespace_adopted=jakarta_import_count > 0,
|
|
1687
1836
|
findings=all_findings,
|
|
1688
1837
|
hibernate=hibernate_strat,
|
|
1689
1838
|
limitations=limitations,
|
sourcecode/path_filters.py
CHANGED
|
@@ -154,5 +154,36 @@ def is_vendor_path(path: str) -> bool:
|
|
|
154
154
|
for part in dir_parts:
|
|
155
155
|
if part in _LIB_SEGMENTS:
|
|
156
156
|
return True
|
|
157
|
+
# Vendored web libraries shipped into a theme/webapp by file name, e.g.
|
|
158
|
+
# jquery-3.5.1.js, jquery-migrate-1.2.1.js, bootstrap.bundle.js. These carry
|
|
159
|
+
# third-party comments (jQuery's own "BUG:" notes) that must not be reported
|
|
160
|
+
# as the project's own code notes.
|
|
161
|
+
if _is_vendored_web_lib(filename) or any(part in _VENDOR_WEB_LIB_DIRS for part in dir_parts):
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
return False
|
|
165
|
+
|
|
157
166
|
|
|
167
|
+
# Stems of common vendored JS/CSS libraries. A web-asset file whose name starts with
|
|
168
|
+
# one of these (optionally followed by a version/qualifier) is third-party, not the
|
|
169
|
+
# project's own source — wherever it physically sits in the tree.
|
|
170
|
+
_VENDOR_WEB_LIB_STEMS: tuple[str, ...] = (
|
|
171
|
+
"jquery", "bootstrap", "angular", "react", "vue", "svelte", "ember",
|
|
172
|
+
"lodash", "underscore", "moment", "d3", "three", "chart", "popper",
|
|
173
|
+
"modernizr", "select2", "datatables", "ckeditor", "tinymce", "fontawesome",
|
|
174
|
+
"font-awesome", "backbone", "knockout", "prototype", "scriptaculous",
|
|
175
|
+
"highcharts", "leaflet", "axios", "polyfill", "babel", "require", "requirejs",
|
|
176
|
+
"handlebars", "mustache", "dojo", "extjs", "swiper", "slick", "fullcalendar",
|
|
177
|
+
)
|
|
178
|
+
# Directory names that hold only vendored web assets.
|
|
179
|
+
_VENDOR_WEB_LIB_DIRS: frozenset[str] = frozenset({"jquery", "bootstrap", "webjars"})
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _is_vendored_web_lib(filename: str) -> bool:
|
|
183
|
+
base = filename.lower()
|
|
184
|
+
for stem in _VENDOR_WEB_LIB_STEMS:
|
|
185
|
+
s = stem.strip()
|
|
186
|
+
# match "jquery.js", "jquery-3.5.1.js", "jquery.min.js", "jquery-ui.js"
|
|
187
|
+
if base == f"{s}.js" or base == f"{s}.css" or base.startswith((f"{s}-", f"{s}.")):
|
|
188
|
+
return True
|
|
158
189
|
return False
|
sourcecode/serializer.py
CHANGED
|
@@ -291,17 +291,65 @@ _JAKARTA_RENAMED_NAMESPACES: tuple[str, ...] = (
|
|
|
291
291
|
)
|
|
292
292
|
|
|
293
293
|
|
|
294
|
+
# Permanent JDK / JSR javax.* namespaces. These are part of the Java SE platform
|
|
295
|
+
# (or standalone JCP specs) and were NEVER renamed to jakarta.* — Spring Boot 3/4
|
|
296
|
+
# still uses them under javax.*. They must be excluded from EVERY javax→jakarta
|
|
297
|
+
# detection rule (dependency coordinates AND import scanning). This is the single
|
|
298
|
+
# source of truth for the allowlist — do NOT re-implement prefix checks elsewhere.
|
|
299
|
+
# Counterpart in migrate_check._JAKARTA_NO_MIGRATE_PREFIXES (import side); keep both
|
|
300
|
+
# in sync. NOTE: `javax.annotation.processing` is permanent (JSR-269, compiler API)
|
|
301
|
+
# but bare `javax.annotation` (JSR-250 EE subset) DID move — handled below.
|
|
302
|
+
_JAVAX_PERMANENT_NAMESPACES: tuple[str, ...] = (
|
|
303
|
+
"javax.cache", # JSR-107 (JCache) — never renamed
|
|
304
|
+
"javax.sql", # JDBC standard extension (Java SE)
|
|
305
|
+
"javax.xml", # JAXP (parsers/transform/xpath/stream/namespace…)
|
|
306
|
+
"javax.naming", # JNDI (Java SE)
|
|
307
|
+
"javax.management", # JMX (Java SE)
|
|
308
|
+
"javax.crypto", # JCE (Java SE)
|
|
309
|
+
"javax.net", # incl. javax.net.ssl (Java SE)
|
|
310
|
+
"javax.security.auth", # JAAS (Java SE)
|
|
311
|
+
"javax.security.cert", # Java SE
|
|
312
|
+
"javax.security.sasl", # Java SE
|
|
313
|
+
"javax.annotation.processing", # JSR-269 compiler API (NOT the JSR-250 subset)
|
|
314
|
+
"javax.tools", # Java SE compiler API
|
|
315
|
+
"javax.lang.model", # Java SE annotation-processing model
|
|
316
|
+
"javax.sound", # Java SE
|
|
317
|
+
"javax.imageio", # Java SE
|
|
318
|
+
"javax.print", # Java SE
|
|
319
|
+
"javax.accessibility", # Java SE
|
|
320
|
+
"javax.swing", # Java SE
|
|
321
|
+
"javax.smartcardio", # Java SE
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _longest_namespace_match(nl: str, namespaces: "tuple[str, ...]") -> int:
|
|
326
|
+
"""Length of the longest namespace in *namespaces* that is a prefix of *nl*.
|
|
327
|
+
|
|
328
|
+
Matches an exact namespace or a dotted sub-package (javax.xml → javax.xml.bind);
|
|
329
|
+
returns 0 when none match.
|
|
330
|
+
"""
|
|
331
|
+
best = 0
|
|
332
|
+
for ns in namespaces:
|
|
333
|
+
if (nl == ns or nl.startswith(ns + ".")) and len(ns) > best:
|
|
334
|
+
best = len(ns)
|
|
335
|
+
return best
|
|
336
|
+
|
|
337
|
+
|
|
294
338
|
def _is_jakarta_renamed_namespace(nl: str) -> bool:
|
|
295
339
|
"""True only for javax.* namespaces actually renamed to jakarta.* in Jakarta EE 9.
|
|
296
340
|
|
|
297
|
-
|
|
341
|
+
Decided by LONGEST-prefix match across the renamed list and the permanent
|
|
342
|
+
JDK/JSR allowlist — the more specific namespace wins. This keeps both nuanced
|
|
343
|
+
cases correct simultaneously:
|
|
344
|
+
- javax.xml.bind (renamed, len 14) beats javax.xml (permanent, len 9) → flag.
|
|
345
|
+
- javax.annotation.processing (permanent, len 27) beats javax.annotation
|
|
346
|
+
(renamed, len 16) → no flag.
|
|
298
347
|
JDK/JSR namespaces that keep javax.* forever (javax.cache, javax.sql, javax.xml
|
|
299
|
-
JAXP, javax.naming, …)
|
|
348
|
+
JAXP, javax.naming, …) never flag. A bare javax.* with no match also never flags.
|
|
300
349
|
"""
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return False
|
|
350
|
+
renamed = _longest_namespace_match(nl, _JAKARTA_RENAMED_NAMESPACES)
|
|
351
|
+
permanent = _longest_namespace_match(nl, _JAVAX_PERMANENT_NAMESPACES)
|
|
352
|
+
return renamed > 0 and renamed >= permanent
|
|
305
353
|
|
|
306
354
|
|
|
307
355
|
def _dep_risk_flags(name: str, version: "Optional[str]") -> list[str]:
|
sourcecode/summarizer.py
CHANGED
|
@@ -296,7 +296,7 @@ class ProjectSummarizer:
|
|
|
296
296
|
_TYPE_LABELS: dict[str, str] = {
|
|
297
297
|
"cli": "CLI",
|
|
298
298
|
"api": "API",
|
|
299
|
-
"web_mvc": "
|
|
299
|
+
"web_mvc": "Server-side MVC web app",
|
|
300
300
|
"webapp": "Web application",
|
|
301
301
|
"library": "Library",
|
|
302
302
|
"monorepo": "Monorepo",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.67.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
|
|
@@ -738,9 +738,21 @@ evidence. `migrate-check` enforces:
|
|
|
738
738
|
usage; Quarkus / Micronaut / Helidon / Jakarta-pure repos report `boot3` as N/A
|
|
739
739
|
(`spring_present: false`), not a contradictory `applicable: true`.
|
|
740
740
|
- **Permanent `javax.*` are never jakarta debt.** JDK/JSR namespaces
|
|
741
|
-
(`javax.
|
|
742
|
-
`javax.management
|
|
743
|
-
jakarta scorer
|
|
741
|
+
(`javax.cache`, `javax.sql`, `javax.xml` JAXP, `javax.crypto`, `javax.naming`,
|
|
742
|
+
`javax.management`, `javax.security.auth`, `javax.annotation.processing`, …) are
|
|
743
|
+
allowlisted across every jakarta scorer (single source of truth:
|
|
744
|
+
`serializer._JAVAX_PERMANENT_NAMESPACES` + `migrate_check._JAKARTA_NO_MIGRATE_PREFIXES`).
|
|
745
|
+
The `javax→jakarta` dependency flag decides via **longest-prefix match**, so
|
|
746
|
+
`javax.xml.bind` (JAXB — moved) flags while `javax.xml` (JAXP) does not, and
|
|
747
|
+
`javax.annotation` (JSR-250 — moved) flags while `javax.annotation.processing`
|
|
748
|
+
(JSR-269) does not. The allowlist never silences a real migration.
|
|
749
|
+
- **`readiness_score` is a traceable aggregate.** It is `min` over the applicable
|
|
750
|
+
**migration** dimensions (`jakarta` / `boot3` / `hibernate`); `jdk_modernization`
|
|
751
|
+
is orthogonal upkeep and is excluded. The exact inputs are in `readiness_aggregate{}`
|
|
752
|
+
and an invariant (`readiness_score == min(applicable migration dims)`) is asserted in
|
|
753
|
+
code. When the Hibernate version is undeclared it is inferred from the Spring Boot
|
|
754
|
+
BOM (Boot ≥3 → Hibernate ≥6 → N/A; Boot 2 → Hibernate 5 → applicable; no BOM →
|
|
755
|
+
`status: unresolved`, never a heuristic score).
|
|
744
756
|
- **Three buckets: blocker ≠ hygiene ≠ test.** Only product (`main`) code counts
|
|
745
757
|
toward `blocking_count`, readiness, and effort. Test harnesses
|
|
746
758
|
(`testsuite/`, `test-framework/`, `integration-arquillian/`, `*-test*`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=PwGPkSaAcBTadA2mRPMQcETHoR9cW1WtQrx9s5Clpqw,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,15 +7,15 @@ 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=lVkoLKPfPPJT5YGr1_IskcAKfQ0Qvfc-zRF5xF8WTjc,281985
|
|
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
|
|
14
|
-
sourcecode/context_summarizer.py,sha256=
|
|
14
|
+
sourcecode/context_summarizer.py,sha256=cI2TZMvEhl0BEma12VtPaX6z03ZVBAetVzuK5GaCOvg,6852
|
|
15
15
|
sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
|
|
16
16
|
sourcecode/contract_pipeline.py,sha256=bNn9SYtwfI1CGKlYGxewexlp7_IWhpwSCae-BMuuVwQ,28895
|
|
17
17
|
sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
|
|
18
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
18
|
+
sourcecode/dependency_analyzer.py,sha256=fQCaWQ7_BNcVgZv8gweJCwJ1CGlXsz8nw_tqN3aHJNE,63952
|
|
19
19
|
sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
|
|
20
20
|
sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4-W3zWE,5322
|
|
21
21
|
sourcecode/env_analyzer.py,sha256=aNTyYgQk5noJDfJU6FmasmESOHfiomyJw5EvZqjy6qc,22213
|
|
@@ -33,10 +33,10 @@ sourcecode/integration_detector.py,sha256=mQulsXN1P-4V_3ueh3xy_N9x3Aqlvm7GQ-3YGq
|
|
|
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=
|
|
36
|
+
sourcecode/migrate_check.py,sha256=psBbL8P7vz8LUtWr0ykoKfUhDb9ss8_J3SB6UUHbMHo,88702
|
|
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=
|
|
39
|
+
sourcecode/path_filters.py,sha256=VnaD9jxZVNzluackNSTCdIddwzAqIseuSCs_A-gpCDM,6898
|
|
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
|
|
@@ -53,7 +53,7 @@ sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
|
53
53
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
54
54
|
sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs,3503
|
|
55
55
|
sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIxkw,89176
|
|
56
|
-
sourcecode/serializer.py,sha256=
|
|
56
|
+
sourcecode/serializer.py,sha256=MSxYZ-_UYDPKMvg-hVk-MnKN-TfrmxXL202siCssa9U,129110
|
|
57
57
|
sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
|
|
58
58
|
sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
|
|
59
59
|
sourcecode/spring_impact.py,sha256=qLwLfItX_o9LU-k_qjhD2hFpTX3PpEQ85TsYTAArzvg,56016
|
|
@@ -61,7 +61,7 @@ sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16
|
|
|
61
61
|
sourcecode/spring_security_audit.py,sha256=XtPJ1SXlZJ8k6VYmaWuAp7Bbir4UmreAL7doIGQ5I7o,20595
|
|
62
62
|
sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM,13307
|
|
63
63
|
sourcecode/spring_tx_analyzer.py,sha256=FdFcyqPp3aT9oJ-PKrnXcTA6s69wdvzG-NBm0GMGPTU,30717
|
|
64
|
-
sourcecode/summarizer.py,sha256=
|
|
64
|
+
sourcecode/summarizer.py,sha256=hV-kPFXXh15GnIvmLZnmVWTEbmr35C-yKVtPiR25U2g,21802
|
|
65
65
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
66
66
|
sourcecode/validation_surface.py,sha256=2Ojfzhw9R4XpemAFqb9RXf4FKyI3DYjGo4Dp0AHZMQo,22600
|
|
67
67
|
sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
|
|
@@ -88,10 +88,10 @@ sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5Tc
|
|
|
88
88
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
89
89
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
90
90
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
91
|
-
sourcecode/mcp/orchestrator.py,sha256=
|
|
92
|
-
sourcecode/mcp/registry.py,sha256=
|
|
91
|
+
sourcecode/mcp/orchestrator.py,sha256=diVoQgn24QmgPL3Ev8Sp6hsvh02OqY3MktHXOzrlodo,36525
|
|
92
|
+
sourcecode/mcp/registry.py,sha256=jLCUG7-m53Fbxqjzpt_OWu6NUp7b46S2ixTDixsTRyo,65975
|
|
93
93
|
sourcecode/mcp/runner.py,sha256=-Dp2qPGRkfNTVen6bKh7WtzQqpcEtsrXoiuajvshlKk,2866
|
|
94
|
-
sourcecode/mcp/server.py,sha256=
|
|
94
|
+
sourcecode/mcp/server.py,sha256=1jwMR0OwJcLi1GoAKT300RSQAhfd_nYLrvABG7s0pUU,63874
|
|
95
95
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
96
96
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
97
97
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -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.
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
109
|
-
sourcecode-1.
|
|
110
|
-
sourcecode-1.
|
|
106
|
+
sourcecode-1.67.0.dist-info/METADATA,sha256=2UUp86qduBBVjyFuifvdUOmahDSseKPyZ8rsy7j2P8g,47341
|
|
107
|
+
sourcecode-1.67.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
108
|
+
sourcecode-1.67.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
109
|
+
sourcecode-1.67.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
110
|
+
sourcecode-1.67.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|