sourcecode 1.35.18__tar.gz → 1.35.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-1.35.18 → sourcecode-1.35.20}/PKG-INFO +3 -3
- {sourcecode-1.35.18 → sourcecode-1.35.20}/README.md +2 -2
- {sourcecode-1.35.18 → sourcecode-1.35.20}/pyproject.toml +1 -1
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/cli.py +220 -5
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/registry.py +52 -1
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/server.py +50 -0
- sourcecode-1.35.20/src/sourcecode/migrate_check.py +434 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/.gitignore +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/.ruff.toml +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/CHANGELOG.md +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/CONTRIBUTING.md +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/LICENSE +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/SECURITY.md +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/raw +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/license.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.35.18 → sourcecode-1.35.20}/src/sourcecode/workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.35.
|
|
3
|
+
Version: 1.35.20
|
|
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
|
|
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
42
42
|
|
|
43
|
-

|
|
44
44
|

|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -114,7 +114,7 @@ pipx install sourcecode
|
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
sourcecode version
|
|
117
|
-
# sourcecode 1.35.
|
|
117
|
+
# sourcecode 1.35.20
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -76,7 +76,7 @@ pipx install sourcecode
|
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
sourcecode version
|
|
79
|
-
# sourcecode 1.35.
|
|
79
|
+
# sourcecode 1.35.20
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sourcecode"
|
|
7
|
-
version = "1.35.
|
|
7
|
+
version = "1.35.20"
|
|
8
8
|
description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -240,6 +240,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
240
240
|
"pr-impact",
|
|
241
241
|
# Class architectural summary
|
|
242
242
|
"explain",
|
|
243
|
+
# Spring Boot 2→3 migration readiness
|
|
244
|
+
"migrate-check",
|
|
243
245
|
}
|
|
244
246
|
)
|
|
245
247
|
|
|
@@ -3720,6 +3722,80 @@ def endpoints_cmd(
|
|
|
3720
3722
|
|
|
3721
3723
|
# ── Spring Semantic Audit ─────────────────────────────────────────────────────
|
|
3722
3724
|
|
|
3725
|
+
|
|
3726
|
+
def _render_spring_audit_github_comment(result: "SpringAuditResult", min_severity: str = "low") -> str: # type: ignore[name-defined]
|
|
3727
|
+
"""Render SpringAuditResult as a GitHub PR comment in Markdown."""
|
|
3728
|
+
from sourcecode.spring_findings import SEVERITY_ORDER
|
|
3729
|
+
|
|
3730
|
+
min_order = SEVERITY_ORDER.get(min_severity, 3)
|
|
3731
|
+
visible = [f for f in result.findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]
|
|
3732
|
+
|
|
3733
|
+
sev = result.summary.get("by_severity", {})
|
|
3734
|
+
total = result.summary.get("total_findings", 0)
|
|
3735
|
+
blocking = sev.get("critical", 0) + sev.get("high", 0)
|
|
3736
|
+
|
|
3737
|
+
_ICONS = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🔵"}
|
|
3738
|
+
_LABELS = {"critical": "CRITICAL", "high": "HIGH", "medium": "MEDIUM", "low": "LOW"}
|
|
3739
|
+
|
|
3740
|
+
if total == 0:
|
|
3741
|
+
status_line = "✅ **Spring Audit — no findings**"
|
|
3742
|
+
elif blocking > 0:
|
|
3743
|
+
status_line = f"🔴 **Spring Audit — {total} finding{'s' if total != 1 else ''} ({blocking} blocking)**"
|
|
3744
|
+
else:
|
|
3745
|
+
status_line = f"🟡 **Spring Audit — {total} finding{'s' if total != 1 else ''} (0 blocking)**"
|
|
3746
|
+
|
|
3747
|
+
lines: list[str] = [status_line, ""]
|
|
3748
|
+
|
|
3749
|
+
if total > 0:
|
|
3750
|
+
severity_counts = []
|
|
3751
|
+
for sev_name in ("critical", "high", "medium", "low"):
|
|
3752
|
+
n = sev.get(sev_name, 0)
|
|
3753
|
+
if n:
|
|
3754
|
+
severity_counts.append(f"{_ICONS[sev_name]} {n} {sev_name}")
|
|
3755
|
+
lines.append("**Severity:** " + " · ".join(severity_counts))
|
|
3756
|
+
lines.append("")
|
|
3757
|
+
|
|
3758
|
+
if not visible:
|
|
3759
|
+
lines.append(f"_No findings at or above `{min_severity}` severity._")
|
|
3760
|
+
return "\n".join(lines)
|
|
3761
|
+
|
|
3762
|
+
lines += [
|
|
3763
|
+
"| Sev | Pattern | File | Symbol | Title |",
|
|
3764
|
+
"|-----|---------|------|--------|-------|",
|
|
3765
|
+
]
|
|
3766
|
+
for f in sorted(visible, key=lambda x: (SEVERITY_ORDER.get(x.severity, 3), x.source_file)):
|
|
3767
|
+
icon = _ICONS.get(f.severity, "")
|
|
3768
|
+
label = _LABELS.get(f.severity, f.severity.upper())
|
|
3769
|
+
short_file = f.source_file.split("/")[-1] if "/" in f.source_file else f.source_file
|
|
3770
|
+
short_sym = f.symbol.split(".")[-1] if "." in f.symbol else f.symbol
|
|
3771
|
+
title_escaped = f.title.replace("|", "\\|")
|
|
3772
|
+
lines.append(f"| {icon} {label} | `{f.pattern_id}` | `{short_file}` | `{short_sym}` | {title_escaped} |")
|
|
3773
|
+
|
|
3774
|
+
lines.append("")
|
|
3775
|
+
|
|
3776
|
+
if visible:
|
|
3777
|
+
lines.append("<details>")
|
|
3778
|
+
lines.append("<summary>Finding details</summary>")
|
|
3779
|
+
lines.append("")
|
|
3780
|
+
for f in sorted(visible, key=lambda x: (SEVERITY_ORDER.get(x.severity, 3), x.source_file)):
|
|
3781
|
+
icon = _ICONS.get(f.severity, "")
|
|
3782
|
+
lines.append(f"### {icon} `{f.pattern_id}` — {f.title}")
|
|
3783
|
+
lines.append(f"**File:** `{f.source_file}` **Symbol:** `{f.symbol}`")
|
|
3784
|
+
lines.append("")
|
|
3785
|
+
lines.append(f.explanation)
|
|
3786
|
+
lines.append("")
|
|
3787
|
+
lines.append(f"**Fix:** {f.fix_hint}")
|
|
3788
|
+
lines.append("")
|
|
3789
|
+
lines.append("</details>")
|
|
3790
|
+
|
|
3791
|
+
lines += [
|
|
3792
|
+
"",
|
|
3793
|
+
f"_Generated by [sourcecode](https://github.com/sourcecode-ai/sourcecode) · "
|
|
3794
|
+
f"scope: {result.scope} · min-severity: {min_severity}_",
|
|
3795
|
+
]
|
|
3796
|
+
return "\n".join(lines)
|
|
3797
|
+
|
|
3798
|
+
|
|
3723
3799
|
@app.command("spring-audit")
|
|
3724
3800
|
def spring_audit_cmd(
|
|
3725
3801
|
path: Path = typer.Argument(
|
|
@@ -3734,7 +3810,7 @@ def spring_audit_cmd(
|
|
|
3734
3810
|
"json",
|
|
3735
3811
|
"--format",
|
|
3736
3812
|
"-f",
|
|
3737
|
-
help="Output format: json (default) or
|
|
3813
|
+
help="Output format: json (default), yaml, or github-comment.",
|
|
3738
3814
|
show_default=True,
|
|
3739
3815
|
),
|
|
3740
3816
|
copy: bool = typer.Option(
|
|
@@ -3756,6 +3832,11 @@ def spring_audit_cmd(
|
|
|
3756
3832
|
help="Minimum severity to include: critical, high, medium, or low (default).",
|
|
3757
3833
|
show_default=True,
|
|
3758
3834
|
),
|
|
3835
|
+
ci: bool = typer.Option(
|
|
3836
|
+
False,
|
|
3837
|
+
"--ci/--no-ci",
|
|
3838
|
+
help="Exit with code 1 if any findings at or above --min-severity are found. For CI/CD gates.",
|
|
3839
|
+
),
|
|
3759
3840
|
) -> None:
|
|
3760
3841
|
"""Spring semantic audit: TX anomalies (TX-001..005) + security surface (SEC-001..003).
|
|
3761
3842
|
|
|
@@ -3770,6 +3851,12 @@ def spring_audit_cmd(
|
|
|
3770
3851
|
SEC-002 CVE-2025-41248: @PreAuthorize on inherited method from generic supertype
|
|
3771
3852
|
SEC-003 @Transactional on @Controller/@RestController (TX in wrong layer)
|
|
3772
3853
|
|
|
3854
|
+
\b
|
|
3855
|
+
CI/CD usage:
|
|
3856
|
+
sourcecode spring-audit . --ci # exit 1 on any finding
|
|
3857
|
+
sourcecode spring-audit . --ci --min-severity high # exit 1 only on high/critical
|
|
3858
|
+
sourcecode spring-audit . --ci --format github-comment # Markdown PR comment + exit 1
|
|
3859
|
+
|
|
3773
3860
|
\b
|
|
3774
3861
|
Examples:
|
|
3775
3862
|
sourcecode spring-audit .
|
|
@@ -3816,15 +3903,27 @@ def spring_audit_cmd(
|
|
|
3816
3903
|
)
|
|
3817
3904
|
raise typer.Exit(code=1)
|
|
3818
3905
|
|
|
3906
|
+
if format not in ("json", "yaml", "github-comment"):
|
|
3907
|
+
_emit_error_json(
|
|
3908
|
+
INVALID_INPUT_CODE,
|
|
3909
|
+
f"Invalid format '{format}'.",
|
|
3910
|
+
hint="format must be one of: json, yaml, github-comment.",
|
|
3911
|
+
expected="json | yaml | github-comment",
|
|
3912
|
+
)
|
|
3913
|
+
raise typer.Exit(code=1)
|
|
3914
|
+
|
|
3819
3915
|
file_list = find_java_files(target)
|
|
3820
3916
|
if not file_list:
|
|
3821
|
-
|
|
3917
|
+
empty_result = SpringAuditResult(
|
|
3822
3918
|
spring_detected=False,
|
|
3823
3919
|
scope=scope,
|
|
3824
3920
|
limitations=["No Java files found in repository — Spring audit requires Java source."],
|
|
3825
3921
|
metadata={"java_files_found": 0},
|
|
3826
|
-
).finalize()
|
|
3827
|
-
|
|
3922
|
+
).finalize()
|
|
3923
|
+
if format == "github-comment":
|
|
3924
|
+
output = _render_spring_audit_github_comment(empty_result, min_severity)
|
|
3925
|
+
else:
|
|
3926
|
+
output = _serialize_dict(empty_result.to_dict(), format)
|
|
3828
3927
|
if output_path is not None:
|
|
3829
3928
|
output_path.write_text(output, encoding="utf-8")
|
|
3830
3929
|
typer.echo("Spring audit written to " + str(output_path), err=True)
|
|
@@ -3883,7 +3982,10 @@ def spring_audit_cmd(
|
|
|
3883
3982
|
except Exception:
|
|
3884
3983
|
pass
|
|
3885
3984
|
|
|
3886
|
-
|
|
3985
|
+
if format == "github-comment":
|
|
3986
|
+
output = _render_spring_audit_github_comment(combined, min_severity)
|
|
3987
|
+
else:
|
|
3988
|
+
output = _serialize_dict(data, format)
|
|
3887
3989
|
|
|
3888
3990
|
if output_path is not None:
|
|
3889
3991
|
output_path.write_text(output, encoding="utf-8")
|
|
@@ -3897,6 +3999,119 @@ def spring_audit_cmd(
|
|
|
3897
3999
|
if _copy_to_clipboard(output):
|
|
3898
4000
|
typer.echo("✓ copied to clipboard", err=True)
|
|
3899
4001
|
|
|
4002
|
+
if ci and combined.findings:
|
|
4003
|
+
raise typer.Exit(code=1)
|
|
4004
|
+
|
|
4005
|
+
|
|
4006
|
+
# ── Spring Boot Migration Check ───────────────────────────────────────────────
|
|
4007
|
+
|
|
4008
|
+
|
|
4009
|
+
@app.command("migrate-check")
|
|
4010
|
+
def migrate_check_cmd(
|
|
4011
|
+
path: Path = typer.Argument(
|
|
4012
|
+
Path("."),
|
|
4013
|
+
help="Repository path to scan (default: current directory)",
|
|
4014
|
+
),
|
|
4015
|
+
output_path: Optional[Path] = typer.Option(
|
|
4016
|
+
None, "--output", "-o",
|
|
4017
|
+
help="Write output to a file instead of stdout.",
|
|
4018
|
+
),
|
|
4019
|
+
format: str = typer.Option(
|
|
4020
|
+
"json",
|
|
4021
|
+
"--format",
|
|
4022
|
+
"-f",
|
|
4023
|
+
help="Output format: json (default) or text.",
|
|
4024
|
+
show_default=True,
|
|
4025
|
+
),
|
|
4026
|
+
copy: bool = typer.Option(
|
|
4027
|
+
False,
|
|
4028
|
+
"--copy",
|
|
4029
|
+
"-c",
|
|
4030
|
+
help="Copy output to system clipboard after a successful run.",
|
|
4031
|
+
),
|
|
4032
|
+
min_severity: str = typer.Option(
|
|
4033
|
+
"low",
|
|
4034
|
+
"--min-severity",
|
|
4035
|
+
help="Minimum severity to include: critical, high, medium, or low (default).",
|
|
4036
|
+
show_default=True,
|
|
4037
|
+
),
|
|
4038
|
+
) -> None:
|
|
4039
|
+
"""Spring Boot 2→3 migration readiness: detect javax→jakarta namespace blockers.
|
|
4040
|
+
|
|
4041
|
+
\b
|
|
4042
|
+
Detects:
|
|
4043
|
+
MIG-001 javax.persistence import (CRITICAL — JPA will not compile)
|
|
4044
|
+
MIG-002 javax.servlet import (HIGH — Servlet API changed)
|
|
4045
|
+
MIG-003 javax.validation import (HIGH — Bean Validation changed)
|
|
4046
|
+
MIG-004 javax.transaction import (HIGH — TX API changed)
|
|
4047
|
+
MIG-005 extends WebSecurityConfigurerAdapter (HIGH — removed in Spring 6)
|
|
4048
|
+
MIG-006 javax.annotation import (MEDIUM)
|
|
4049
|
+
MIG-007 javax.inject import (MEDIUM)
|
|
4050
|
+
MIG-008 javax.ws.rs import (MEDIUM — JAX-RS changed)
|
|
4051
|
+
|
|
4052
|
+
\b
|
|
4053
|
+
Examples:
|
|
4054
|
+
sourcecode migrate-check .
|
|
4055
|
+
sourcecode migrate-check /path/to/repo --format text
|
|
4056
|
+
sourcecode migrate-check . --min-severity high
|
|
4057
|
+
sourcecode migrate-check . --output migration.json
|
|
4058
|
+
"""
|
|
4059
|
+
from sourcecode.repository_ir import find_java_files
|
|
4060
|
+
from sourcecode.migrate_check import run_migrate_check
|
|
4061
|
+
|
|
4062
|
+
target = path.resolve()
|
|
4063
|
+
if not target.exists() or not target.is_dir():
|
|
4064
|
+
_emit_error_json(
|
|
4065
|
+
INVALID_INPUT_CODE,
|
|
4066
|
+
f"'{target}' is not a valid directory.",
|
|
4067
|
+
path=str(target),
|
|
4068
|
+
hint="Pass an existing repository directory.",
|
|
4069
|
+
expected="A directory path.",
|
|
4070
|
+
)
|
|
4071
|
+
raise typer.Exit(code=1)
|
|
4072
|
+
|
|
4073
|
+
if format not in ("json", "text"):
|
|
4074
|
+
_emit_error_json(
|
|
4075
|
+
INVALID_INPUT_CODE,
|
|
4076
|
+
f"Invalid format '{format}'.",
|
|
4077
|
+
hint="format must be one of: json, text.",
|
|
4078
|
+
expected="json | text",
|
|
4079
|
+
)
|
|
4080
|
+
raise typer.Exit(code=1)
|
|
4081
|
+
|
|
4082
|
+
if min_severity not in ("critical", "high", "medium", "low"):
|
|
4083
|
+
_emit_error_json(
|
|
4084
|
+
INVALID_INPUT_CODE,
|
|
4085
|
+
f"Invalid min-severity '{min_severity}'.",
|
|
4086
|
+
hint="min-severity must be one of: critical, high, medium, low.",
|
|
4087
|
+
expected="critical | high | medium | low",
|
|
4088
|
+
)
|
|
4089
|
+
raise typer.Exit(code=1)
|
|
4090
|
+
|
|
4091
|
+
file_list = find_java_files(target)
|
|
4092
|
+
report = run_migrate_check(file_list, target, min_severity=min_severity)
|
|
4093
|
+
|
|
4094
|
+
if format == "text":
|
|
4095
|
+
output = report.to_text(min_severity=min_severity)
|
|
4096
|
+
else:
|
|
4097
|
+
output = _serialize_dict(report.to_dict(), "json")
|
|
4098
|
+
|
|
4099
|
+
if output_path is not None:
|
|
4100
|
+
output_path.write_text(output, encoding="utf-8")
|
|
4101
|
+
total = report.summary.get("total_findings", 0)
|
|
4102
|
+
typer.echo(
|
|
4103
|
+
f"Migration check written to {output_path} "
|
|
4104
|
+
f"(score: {report.readiness_score}/100, {total} findings)",
|
|
4105
|
+
err=True,
|
|
4106
|
+
)
|
|
4107
|
+
else:
|
|
4108
|
+
sys.stdout.buffer.write(output.encode("utf-8"))
|
|
4109
|
+
sys.stdout.buffer.write(b"\n")
|
|
4110
|
+
sys.stdout.buffer.flush()
|
|
4111
|
+
if copy:
|
|
4112
|
+
if _copy_to_clipboard(output):
|
|
4113
|
+
typer.echo("✓ copied to clipboard", err=True)
|
|
4114
|
+
|
|
3900
4115
|
|
|
3901
4116
|
# ── Spring Impact Chain ───────────────────────────────────────────────────────
|
|
3902
4117
|
|
|
@@ -1221,6 +1221,7 @@ _MCP_HIDDEN_CANONICAL_TOOLS: frozenset[str] = frozenset({
|
|
|
1221
1221
|
# Listed here so validate_registry() skips CLI param-drift checks on the alias.
|
|
1222
1222
|
"spring_audit", # curated: repo_path + scope + min_severity only (strips output_path/format/copy)
|
|
1223
1223
|
"impact_chain", # curated: repo_path + symbol + depth + query_type with choices
|
|
1224
|
+
"migrate_check", # curated: repo_path + min_severity only (strips output_path/format/copy/ci)
|
|
1224
1225
|
# MCP self-management (an agent is not the MCP client admin)
|
|
1225
1226
|
"mcp_init",
|
|
1226
1227
|
"mcp_serve",
|
|
@@ -1349,7 +1350,57 @@ query_type: "impact" (default) | "events"
|
|
|
1349
1350
|
docstring_override=_IMPACT_CHAIN_DOC,
|
|
1350
1351
|
)
|
|
1351
1352
|
|
|
1352
|
-
|
|
1353
|
+
_MIGRATE_CHECK_DOC = """\
|
|
1354
|
+
Spring Boot 2→3 migration readiness: javax→jakarta namespace blockers. JAVA ONLY.
|
|
1355
|
+
|
|
1356
|
+
When to call: when asked about Spring Boot migration readiness, javax vs jakarta imports,
|
|
1357
|
+
or upgrading from Spring Boot 2.x to 3.x. Use BEFORE get_spring_audit when the goal
|
|
1358
|
+
is migration planning rather than ongoing Spring semantic audit.
|
|
1359
|
+
Do NOT call on non-Java repositories — returns readiness_score=100 with no findings.
|
|
1360
|
+
|
|
1361
|
+
Rules detected:
|
|
1362
|
+
MIG-001 critical — javax.persistence imports (JPA; will not compile after migration)
|
|
1363
|
+
MIG-002 high — javax.servlet imports (Servlet API changed)
|
|
1364
|
+
MIG-003 high — javax.validation imports (Bean Validation changed)
|
|
1365
|
+
MIG-004 high — javax.transaction imports (TX API changed)
|
|
1366
|
+
MIG-005 high — extends WebSecurityConfigurerAdapter (removed in Spring Security 6)
|
|
1367
|
+
MIG-006 medium — javax.annotation imports (CDI annotations)
|
|
1368
|
+
MIG-007 medium — javax.inject imports (DI annotations)
|
|
1369
|
+
MIG-008 medium — javax.ws.rs imports (JAX-RS API)
|
|
1370
|
+
|
|
1371
|
+
Returns: schema_version, readiness_score (0–100; 100=ready to migrate), blocking_count,
|
|
1372
|
+
estimated_effort_days, spring_boot_2_detected, summary (total_findings, affected_files,
|
|
1373
|
+
by_severity, by_rule), findings[], limitations, metadata.
|
|
1374
|
+
findings fields: id, rule_id, severity, title, source_file, first_line,
|
|
1375
|
+
imports_found, explanation, fix_hint.
|
|
1376
|
+
|
|
1377
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
1378
|
+
min_severity: "low" (default) | "medium" | "high" | "critical" — filter threshold.
|
|
1379
|
+
"""
|
|
1380
|
+
|
|
1381
|
+
migrate_check = _alias_spec(
|
|
1382
|
+
"migrate_check",
|
|
1383
|
+
"Spring Boot 2→3 migration readiness: javax→jakarta blockers. JAVA ONLY.",
|
|
1384
|
+
("migrate-check",),
|
|
1385
|
+
(
|
|
1386
|
+
ToolParamSpec("repo_path", "argument", str, required=False, default=".", is_path=True,
|
|
1387
|
+
help="Absolute path to the Java repository."),
|
|
1388
|
+
ToolParamSpec("min_severity", "option", str, required=False, default="low",
|
|
1389
|
+
option_names=("--min-severity",), choices=("low", "medium", "high", "critical"),
|
|
1390
|
+
help="low (default) | medium | high | critical"),
|
|
1391
|
+
),
|
|
1392
|
+
lambda inputs: [
|
|
1393
|
+
"migrate-check",
|
|
1394
|
+
str(inputs.get("repo_path", ".")),
|
|
1395
|
+
"--min-severity", str(inputs.get("min_severity", "low")),
|
|
1396
|
+
],
|
|
1397
|
+
supported_targets=("repo_path",),
|
|
1398
|
+
unsupported_targets=("file_path",),
|
|
1399
|
+
validator=validate_repo_path,
|
|
1400
|
+
docstring_override=_MIGRATE_CHECK_DOC,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
return [spring_audit, impact_chain, migrate_check]
|
|
1353
1404
|
|
|
1354
1405
|
|
|
1355
1406
|
@lru_cache(maxsize=1)
|
|
@@ -649,6 +649,56 @@ def get_spring_audit(repo_path: str = ".", scope: str = "all") -> dict:
|
|
|
649
649
|
)
|
|
650
650
|
|
|
651
651
|
|
|
652
|
+
@mcp.tool()
|
|
653
|
+
def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") -> dict:
|
|
654
|
+
"""Spring Boot 2→3 migration readiness: javax→jakarta namespace blockers. JAVA ONLY.
|
|
655
|
+
|
|
656
|
+
When to call: when asked about Spring Boot migration readiness, javax vs jakarta imports,
|
|
657
|
+
or upgrading from Spring Boot 2.x to 3.x. Call this BEFORE get_spring_audit when
|
|
658
|
+
the goal is migration planning — not ongoing audit.
|
|
659
|
+
Do NOT call on non-Java repositories — returns readiness_score=100 with no findings.
|
|
660
|
+
|
|
661
|
+
Maps to: sourcecode migrate-check <repo_path> --min-severity <min_severity>
|
|
662
|
+
Returns: MigrationReport with schema_version, readiness_score (0–100; 100=ready to migrate),
|
|
663
|
+
blocking_count, estimated_effort_days, spring_boot_2_detected,
|
|
664
|
+
summary (total_findings, affected_files, by_severity, by_rule),
|
|
665
|
+
findings[], limitations, metadata.
|
|
666
|
+
findings fields: id, rule_id, severity, title, source_file, first_line,
|
|
667
|
+
imports_found, explanation, fix_hint.
|
|
668
|
+
Rules:
|
|
669
|
+
MIG-001 critical — javax.persistence (JPA, will not compile after migration)
|
|
670
|
+
MIG-002 high — javax.servlet (Servlet API)
|
|
671
|
+
MIG-003 high — javax.validation (Bean Validation)
|
|
672
|
+
MIG-004 high — javax.transaction (TX API)
|
|
673
|
+
MIG-005 high — extends WebSecurityConfigurerAdapter (removed in Spring Security 6)
|
|
674
|
+
MIG-006 medium — javax.annotation (CDI annotations)
|
|
675
|
+
MIG-007 medium — javax.inject (DI annotations)
|
|
676
|
+
MIG-008 medium — javax.ws.rs (JAX-RS API)
|
|
677
|
+
|
|
678
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
679
|
+
min_severity: "low" (default) | "medium" | "high" | "critical" — filter threshold.
|
|
680
|
+
"""
|
|
681
|
+
_raw = repo_path
|
|
682
|
+
try:
|
|
683
|
+
if not isinstance(repo_path, str):
|
|
684
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
685
|
+
if min_severity not in ("critical", "high", "medium", "low"):
|
|
686
|
+
return _err(
|
|
687
|
+
f"Invalid min_severity '{min_severity}' — must be one of: critical, high, medium, low",
|
|
688
|
+
"INVALID_ARGUMENT",
|
|
689
|
+
)
|
|
690
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
691
|
+
_path_err = _check_repo_path(repo_path)
|
|
692
|
+
if _path_err is not None:
|
|
693
|
+
return _path_err
|
|
694
|
+
return _execute(["migrate-check", repo_path, "--min-severity", min_severity])
|
|
695
|
+
except Exception as exc:
|
|
696
|
+
return _err(
|
|
697
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
698
|
+
"INTERNAL_ERROR",
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
|
|
652
702
|
@mcp.tool()
|
|
653
703
|
def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) -> dict:
|
|
654
704
|
"""Spring impact-chain: systemic blast radius of a symbol with TX/SEC semantic enrichment. JAVA/SPRING ONLY.
|