sourcecode 1.44.0__py3-none-any.whl → 1.46.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 +57 -2
- sourcecode/mcp/orchestrator.py +209 -0
- sourcecode/mcp/server.py +76 -4
- sourcecode/validation_surface.py +15 -0
- {sourcecode-1.44.0.dist-info → sourcecode-1.46.0.dist-info}/METADATA +2 -2
- {sourcecode-1.44.0.dist-info → sourcecode-1.46.0.dist-info}/RECORD +10 -10
- {sourcecode-1.44.0.dist-info → sourcecode-1.46.0.dist-info}/WHEEL +0 -0
- {sourcecode-1.44.0.dist-info → sourcecode-1.46.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.44.0.dist-info → sourcecode-1.46.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -3685,6 +3685,10 @@ def impact_cmd(
|
|
|
3685
3685
|
"-c",
|
|
3686
3686
|
help="Copy output to system clipboard after a successful run. No-op when --output is used or clipboard is unavailable.",
|
|
3687
3687
|
),
|
|
3688
|
+
no_cache: bool = typer.Option(
|
|
3689
|
+
False, "--no-cache",
|
|
3690
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
3691
|
+
),
|
|
3688
3692
|
) -> None:
|
|
3689
3693
|
"""Blast-radius analysis: who calls this class and what breaks if it changes?
|
|
3690
3694
|
|
|
@@ -3836,6 +3840,10 @@ def endpoints_cmd(
|
|
|
3836
3840
|
None, "--limit", "-n",
|
|
3837
3841
|
help="Maximum number of endpoints to return.",
|
|
3838
3842
|
),
|
|
3843
|
+
no_cache: bool = typer.Option(
|
|
3844
|
+
False, "--no-cache",
|
|
3845
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
3846
|
+
),
|
|
3839
3847
|
) -> None:
|
|
3840
3848
|
"""Extract REST API endpoint surface from Java source files.
|
|
3841
3849
|
|
|
@@ -3899,13 +3907,26 @@ def endpoints_cmd(
|
|
|
3899
3907
|
if limit is not None and limit > 0:
|
|
3900
3908
|
endpoints_list = endpoints_list[:limit]
|
|
3901
3909
|
if path_prefix or controller or limit is not None:
|
|
3910
|
+
# Preserve the repo-wide aggregates before overwriting them with the
|
|
3911
|
+
# filtered counts, so a --limit/--filter request stays internally
|
|
3912
|
+
# coherent (total must match the security counters it is reported with).
|
|
3913
|
+
_no_sec_before = data.get("no_security_signal")
|
|
3914
|
+
_undoc_before = data.get("undocumented")
|
|
3915
|
+
_no_sec_after = sum(
|
|
3916
|
+
1 for e in endpoints_list
|
|
3917
|
+
if e.get("security", {}).get("policy") == "none_detected"
|
|
3918
|
+
)
|
|
3902
3919
|
data["endpoints"] = endpoints_list
|
|
3903
3920
|
data["total"] = len(endpoints_list)
|
|
3921
|
+
data["no_security_signal"] = _no_sec_after
|
|
3922
|
+
data["undocumented"] = _no_sec_after
|
|
3904
3923
|
data["_filter"] = {
|
|
3905
3924
|
"path_prefix": path_prefix,
|
|
3906
3925
|
"controller": controller,
|
|
3907
3926
|
"limit": limit,
|
|
3908
3927
|
"total_before_filter": _total_before,
|
|
3928
|
+
"no_security_signal_before_filter": _no_sec_before,
|
|
3929
|
+
"undocumented_before_filter": _undoc_before,
|
|
3909
3930
|
}
|
|
3910
3931
|
|
|
3911
3932
|
output = _serialize_dict(data, format)
|
|
@@ -3948,6 +3969,10 @@ def validation_cmd(
|
|
|
3948
3969
|
False, "--gaps-only",
|
|
3949
3970
|
help="Report only endpoints/fields with no declared validation (the gaps section).",
|
|
3950
3971
|
),
|
|
3972
|
+
no_cache: bool = typer.Option(
|
|
3973
|
+
False, "--no-cache",
|
|
3974
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
3975
|
+
),
|
|
3951
3976
|
) -> None:
|
|
3952
3977
|
"""Map request-body validation per endpoint (constraints + custom validators).
|
|
3953
3978
|
|
|
@@ -3997,14 +4022,21 @@ def validation_cmd(
|
|
|
3997
4022
|
g for g in data.get("gaps", [])
|
|
3998
4023
|
if str(g.get("path", "")).startswith(path_prefix)
|
|
3999
4024
|
]
|
|
4025
|
+
_note = data.get("note")
|
|
4000
4026
|
if gaps_only:
|
|
4001
4027
|
data = {
|
|
4002
4028
|
"gaps": data.get("gaps", []),
|
|
4003
4029
|
"summary": data.get("summary", {}),
|
|
4004
4030
|
}
|
|
4031
|
+
if _note:
|
|
4032
|
+
data["note"] = _note
|
|
4005
4033
|
|
|
4006
4034
|
output = _serialize_dict(data, format)
|
|
4007
4035
|
_summary = data.get("summary", {})
|
|
4036
|
+
# Human-facing heads-up when the result is empty purely because no OpenAPI
|
|
4037
|
+
# spec was found — otherwise the all-zero JSON reads as a false negative.
|
|
4038
|
+
if _note:
|
|
4039
|
+
typer.echo(f"Note: {_note}", err=True)
|
|
4008
4040
|
_emit_command_output(
|
|
4009
4041
|
output, output_path, copy,
|
|
4010
4042
|
success_msg=f"Validation surface written to {output_path} "
|
|
@@ -4133,6 +4165,10 @@ def spring_audit_cmd(
|
|
|
4133
4165
|
"--ci/--no-ci",
|
|
4134
4166
|
help="Exit with code 1 if any findings at or above --min-severity are found. For CI/CD gates.",
|
|
4135
4167
|
),
|
|
4168
|
+
no_cache: bool = typer.Option(
|
|
4169
|
+
False, "--no-cache",
|
|
4170
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
4171
|
+
),
|
|
4136
4172
|
) -> None:
|
|
4137
4173
|
"""Spring semantic audit: TX anomalies (TX-001..005) + security surface (SEC-001..003).
|
|
4138
4174
|
|
|
@@ -4315,6 +4351,10 @@ def migrate_check_cmd(
|
|
|
4315
4351
|
help="Minimum severity to include: critical, high, medium, or low (default).",
|
|
4316
4352
|
show_default=True,
|
|
4317
4353
|
),
|
|
4354
|
+
no_cache: bool = typer.Option(
|
|
4355
|
+
False, "--no-cache",
|
|
4356
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
4357
|
+
),
|
|
4318
4358
|
) -> None:
|
|
4319
4359
|
"""Spring Boot 2→3 migration readiness: detect javax→jakarta namespace blockers.
|
|
4320
4360
|
|
|
@@ -4423,6 +4463,10 @@ def impact_chain_cmd(
|
|
|
4423
4463
|
help="Query type: impact (default) or events.",
|
|
4424
4464
|
show_default=True,
|
|
4425
4465
|
),
|
|
4466
|
+
no_cache: bool = typer.Option(
|
|
4467
|
+
False, "--no-cache",
|
|
4468
|
+
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
4469
|
+
),
|
|
4426
4470
|
) -> None:
|
|
4427
4471
|
"""Spring impact-chain: systemic blast radius of a symbol with TX/SEC enrichment.
|
|
4428
4472
|
|
|
@@ -6135,8 +6179,19 @@ def cache_clear_cmd(
|
|
|
6135
6179
|
_clear_ris = include_ris or all_
|
|
6136
6180
|
if not yes:
|
|
6137
6181
|
_ris_note = " (including RIS)" if _clear_ris else " (RIS preserved — use --all to also clear it)"
|
|
6138
|
-
|
|
6139
|
-
|
|
6182
|
+
# P1: never block on stdin in a non-interactive context (CI, MCP, pipes).
|
|
6183
|
+
# The interactive prompt is only meaningful on a TTY; elsewhere it would
|
|
6184
|
+
# hang indefinitely waiting for input. Treat non-interactive as confirmed
|
|
6185
|
+
# (clear is idempotent cleanup; RIS is preserved unless --all is passed).
|
|
6186
|
+
if sys.stdin.isatty():
|
|
6187
|
+
import click as _click
|
|
6188
|
+
_click.confirm(f"Delete all cache files for {target}{_ris_note}?", abort=True, err=True)
|
|
6189
|
+
else:
|
|
6190
|
+
typer.echo(
|
|
6191
|
+
f"Non-interactive: clearing cache for {target}{_ris_note}. "
|
|
6192
|
+
"Pass --yes to silence this notice.",
|
|
6193
|
+
err=True,
|
|
6194
|
+
)
|
|
6140
6195
|
removed = _cm.clear(target, clear_ris=_clear_ris)
|
|
6141
6196
|
typer.echo(f"Removed {removed} file(s).", err=True)
|
|
6142
6197
|
|
sourcecode/mcp/orchestrator.py
CHANGED
|
@@ -6,6 +6,20 @@ Converts the MCP from a flat tool collection into a guided agent operating syste
|
|
|
6
6
|
- run_pr_review_flow: auto-chains delta + review_pr + blast radius
|
|
7
7
|
- run_bug_investigation_flow: auto-chains fix_bug + impact + IR context
|
|
8
8
|
- run_feature_flow: auto-chains context + endpoints + delta + structural awareness
|
|
9
|
+
- run_migrate_flow: wraps migrate-check; Spring Boot 2→3 readiness in one call
|
|
10
|
+
- run_security_audit_flow: wraps spring-audit + endpoints; config-less blind-spot warning
|
|
11
|
+
|
|
12
|
+
High-value Java/Spring flow presets (audit 2026-06-16, repo SAINT) — implemented:
|
|
13
|
+
1. run_migrate_flow — preset over `migrate-check`, the primary entry point for
|
|
14
|
+
Spring Boot 2→3 planning. Lifts readiness_score, blocking_count,
|
|
15
|
+
estimated_effort_days and the per-target breakdown to a top-level headline.
|
|
16
|
+
2. run_security_audit_flow — preset over `spring-audit` + `endpoints`. Detects the
|
|
17
|
+
config-less case (no sourcecode.config.json + every endpoint none_detected) and
|
|
18
|
+
emits a warning plus a ready-to-paste config hint instead of a misleading 100%
|
|
19
|
+
unsecured surface.
|
|
20
|
+
3. apply_orchestration_rules R5/R6 — inject these presets when the detected intent
|
|
21
|
+
maps to migration (INTENT_MIGRATION) or security audit (INTENT_SECURITY_AUDIT),
|
|
22
|
+
mirroring the existing R2 java_no_endpoints rule.
|
|
9
23
|
"""
|
|
10
24
|
from __future__ import annotations
|
|
11
25
|
|
|
@@ -32,6 +46,8 @@ SESSION_READY_FOR_REVIEW = "READY_FOR_REVIEW" # flow complete
|
|
|
32
46
|
# ---------------------------------------------------------------------------
|
|
33
47
|
|
|
34
48
|
INTENT_PR_REVIEW = "pr_review"
|
|
49
|
+
INTENT_MIGRATION = "migration"
|
|
50
|
+
INTENT_SECURITY_AUDIT = "security_audit"
|
|
35
51
|
INTENT_BUG_INVESTIGATION = "bug_investigation"
|
|
36
52
|
INTENT_FEATURE_IMPLEMENTATION = "feature_implementation"
|
|
37
53
|
INTENT_REFACTOR = "refactor"
|
|
@@ -44,6 +60,8 @@ INTENT_ORIENTATION = "orientation"
|
|
|
44
60
|
|
|
45
61
|
WORKFLOW_SEQUENCES: dict[str, list[str]] = {
|
|
46
62
|
INTENT_PR_REVIEW: ["get_delta", "review_pr_context", "get_impact_context"],
|
|
63
|
+
INTENT_MIGRATION: ["get_migration_readiness"],
|
|
64
|
+
INTENT_SECURITY_AUDIT: ["get_spring_audit", "get_endpoints"],
|
|
47
65
|
INTENT_BUG_INVESTIGATION: ["fix_bug_context", "get_impact_context"],
|
|
48
66
|
INTENT_FEATURE_IMPLEMENTATION: ["get_compact_context", "get_endpoints", "get_delta"],
|
|
49
67
|
INTENT_REFACTOR: ["get_agent_context", "modernize_context", "get_ir_summary"],
|
|
@@ -53,6 +71,8 @@ WORKFLOW_SEQUENCES: dict[str, list[str]] = {
|
|
|
53
71
|
|
|
54
72
|
WORKFLOW_DESCRIPTIONS: dict[str, str] = {
|
|
55
73
|
INTENT_PR_REVIEW: "PR review: delta → execution paths → blast radius of changed classes",
|
|
74
|
+
INTENT_MIGRATION: "Spring Boot 2→3 migration: readiness score → javax→jakarta blockers → effort estimate",
|
|
75
|
+
INTENT_SECURITY_AUDIT: "Security audit: Spring TX/security findings → endpoint authorization surface",
|
|
56
76
|
INTENT_BUG_INVESTIGATION: "Bug investigation: risk-ranked files → impact of suspect class",
|
|
57
77
|
INTENT_FEATURE_IMPLEMENTATION: "Feature implementation: context → API surface → recent changes",
|
|
58
78
|
INTENT_REFACTOR: "Refactor: deep context → modernization opportunities → IR coupling",
|
|
@@ -62,6 +82,8 @@ WORKFLOW_DESCRIPTIONS: dict[str, str] = {
|
|
|
62
82
|
|
|
63
83
|
FLOW_RUNNERS: dict[str, str] = {
|
|
64
84
|
INTENT_PR_REVIEW: "run_pr_review_flow",
|
|
85
|
+
INTENT_MIGRATION: "run_migrate_flow",
|
|
86
|
+
INTENT_SECURITY_AUDIT: "run_security_audit_flow",
|
|
65
87
|
INTENT_BUG_INVESTIGATION: "run_bug_investigation_flow",
|
|
66
88
|
INTENT_FEATURE_IMPLEMENTATION: "run_feature_flow",
|
|
67
89
|
INTENT_REFACTOR: "run_feature_flow",
|
|
@@ -78,6 +100,16 @@ _INTENT_PATTERNS: list[tuple[str, list[str]]] = [
|
|
|
78
100
|
r"\bpr\b", r"pull request", r"review pr", r"\bdiff\b", r"merge request",
|
|
79
101
|
r"code review", r"changes in branch", r"review.*branch", r"branch.*review",
|
|
80
102
|
]),
|
|
103
|
+
(INTENT_MIGRATION, [
|
|
104
|
+
r"\bmigrat", r"spring.?boot.?[23]", r"spring boot 2.*3", r"boot 2.*3",
|
|
105
|
+
r"\bjakarta\b", r"\bjavax\b", r"javax.*jakarta", r"namespace.*(migrat|change)",
|
|
106
|
+
r"2\s*(?:->|to|→|–|—)\s*3", r"\bupgrade\b.*(spring|boot|jakarta|java)",
|
|
107
|
+
]),
|
|
108
|
+
(INTENT_SECURITY_AUDIT, [
|
|
109
|
+
r"security audit", r"audit.*security", r"security.*surface",
|
|
110
|
+
r"\bauthoriz", r"\bpreauthorize\b", r"\bsecured\b", r"access control",
|
|
111
|
+
r"who can (?:call|access)", r"endpoint.*(secur|auth)", r"(secur|auth).*endpoint",
|
|
112
|
+
]),
|
|
81
113
|
(INTENT_BUG_INVESTIGATION, [
|
|
82
114
|
r"\bbug\b", r"\berror\b", r"\bexception\b", r"\bcrash\b", r"\bnpe\b",
|
|
83
115
|
r"\bfix\b", r"\bbroken\b", r"\bfail(s|ing)?\b", r"stack.?trace",
|
|
@@ -145,6 +177,9 @@ def apply_orchestration_rules(
|
|
|
145
177
|
R2 java_no_endpoints → prepend get_endpoints (api_surface must exist first)
|
|
146
178
|
R3 large_repo (>1000) → note RIS path preferred (informational, no seq change)
|
|
147
179
|
R4 no_symptom_bug_flow → quality warning issued by caller (not a seq change)
|
|
180
|
+
R5 migration_intent (Java) → ensure get_migration_readiness leads the sequence
|
|
181
|
+
R6 security_audit_intent (Java) → ensure get_endpoints precedes the audit so the
|
|
182
|
+
authorization surface is available when findings are interpreted
|
|
148
183
|
"""
|
|
149
184
|
seq = list(sequence)
|
|
150
185
|
rules: list[str] = []
|
|
@@ -163,6 +198,17 @@ def apply_orchestration_rules(
|
|
|
163
198
|
if repo_class_count > 1000:
|
|
164
199
|
rules.append("R3:large_repo→RIS_path_preferred")
|
|
165
200
|
|
|
201
|
+
# R5: migration intent on a Java repo → migration readiness is the entry point.
|
|
202
|
+
if intent == INTENT_MIGRATION and is_java and "get_migration_readiness" not in seq:
|
|
203
|
+
seq.insert(0, "get_migration_readiness")
|
|
204
|
+
rules.append("R5:migration_intent→prepend_migration_readiness")
|
|
205
|
+
|
|
206
|
+
# R6: security-audit intent on a Java repo → endpoints surface must precede the
|
|
207
|
+
# audit (mirrors R2: the authorization surface has to exist first).
|
|
208
|
+
if intent == INTENT_SECURITY_AUDIT and is_java and "get_endpoints" not in seq:
|
|
209
|
+
seq.insert(0, "get_endpoints")
|
|
210
|
+
rules.append("R6:security_audit_intent→prepend_get_endpoints")
|
|
211
|
+
|
|
166
212
|
return seq, rules
|
|
167
213
|
|
|
168
214
|
|
|
@@ -706,3 +752,166 @@ def run_feature_flow_impl(repo_path: str, feature_description: str = "") -> dict
|
|
|
706
752
|
"tools_suggested_to_agent": 0,
|
|
707
753
|
},
|
|
708
754
|
}
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
# ---------------------------------------------------------------------------
|
|
758
|
+
# Flow: Spring Boot 2→3 Migration
|
|
759
|
+
# ---------------------------------------------------------------------------
|
|
760
|
+
|
|
761
|
+
def run_migrate_flow_impl(repo_path: str, min_severity: str = "low") -> dict[str, Any]:
|
|
762
|
+
"""Migration Flow: Spring Boot 2→3 readiness in one call.
|
|
763
|
+
|
|
764
|
+
Primary high-value entry point for migration planning. Wraps ``migrate-check``
|
|
765
|
+
and lifts the headline numbers (readiness_score, blocking_count,
|
|
766
|
+
estimated_effort_days, per-target breakdown) to the top level so the agent
|
|
767
|
+
can plan a 2→3 upgrade without parsing the full report.
|
|
768
|
+
"""
|
|
769
|
+
t0 = time.monotonic()
|
|
770
|
+
steps: list[str] = []
|
|
771
|
+
quality_warnings: list[str] = []
|
|
772
|
+
output: dict[str, Any] = {}
|
|
773
|
+
|
|
774
|
+
if not _is_java_repo(repo_path):
|
|
775
|
+
quality_warnings.append(
|
|
776
|
+
"not_a_java_repo: migration analysis targets Spring Boot / Java repos. "
|
|
777
|
+
"Result will be empty (readiness_score=100, no findings)."
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
report = _exec(["migrate-check", repo_path, "--min-severity", min_severity])
|
|
781
|
+
steps.append(f"get_migration_readiness(min_severity={min_severity})")
|
|
782
|
+
headline: dict[str, Any] = {}
|
|
783
|
+
if "_exec_error" not in report:
|
|
784
|
+
output["migration_readiness"] = report
|
|
785
|
+
# Lift the planning-relevant headline numbers to the top level.
|
|
786
|
+
for key in (
|
|
787
|
+
"readiness_score", "blocking_count", "estimated_effort_days",
|
|
788
|
+
"spring_boot_2_detected",
|
|
789
|
+
):
|
|
790
|
+
if key in report:
|
|
791
|
+
headline[key] = report[key]
|
|
792
|
+
_summary = report.get("summary", {})
|
|
793
|
+
if isinstance(_summary, dict):
|
|
794
|
+
headline["total_findings"] = _summary.get("total_findings")
|
|
795
|
+
headline["affected_files"] = _summary.get("affected_files")
|
|
796
|
+
headline["by_severity"] = _summary.get("by_severity")
|
|
797
|
+
headline["by_target"] = _summary.get("by_target") or _summary.get("by_rule")
|
|
798
|
+
else:
|
|
799
|
+
quality_warnings.append(f"migrate_check_failed: {report['_exec_error']}")
|
|
800
|
+
|
|
801
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
"session_state": SESSION_READY_FOR_REVIEW,
|
|
805
|
+
"flow": "migration",
|
|
806
|
+
"min_severity": min_severity,
|
|
807
|
+
"headline": headline,
|
|
808
|
+
"steps_executed": steps,
|
|
809
|
+
"quality_warnings": quality_warnings,
|
|
810
|
+
"consolidated_output": output,
|
|
811
|
+
"session_meta": {
|
|
812
|
+
"ttfca_ms": ttfca_ms,
|
|
813
|
+
"steps_auto_executed": len(steps),
|
|
814
|
+
"tools_suggested_to_agent": 0,
|
|
815
|
+
},
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
# ---------------------------------------------------------------------------
|
|
820
|
+
# Flow: Security Audit
|
|
821
|
+
# ---------------------------------------------------------------------------
|
|
822
|
+
|
|
823
|
+
def _endpoint_security_coverage(endpoints: dict[str, Any]) -> tuple[int, int]:
|
|
824
|
+
"""Return (total_endpoints, none_detected) from an endpoints result."""
|
|
825
|
+
if "_exec_error" in endpoints:
|
|
826
|
+
return 0, 0
|
|
827
|
+
data = endpoints.get("data", endpoints) if isinstance(endpoints, dict) else {}
|
|
828
|
+
total = int(data.get("total", 0) or 0)
|
|
829
|
+
none_detected = int(data.get("no_security_signal", 0) or 0)
|
|
830
|
+
return total, none_detected
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def run_security_audit_flow_impl(repo_path: str, scope: str = "all") -> dict[str, Any]:
|
|
834
|
+
"""Security Audit Flow: Spring findings + endpoint authorization surface.
|
|
835
|
+
|
|
836
|
+
Wraps ``spring-audit`` (TX + security findings) and ``endpoints`` (authorization
|
|
837
|
+
surface) in one call. Auto-handles the config-less case: when no
|
|
838
|
+
``sourcecode.config.json`` is present and every endpoint reads as
|
|
839
|
+
``none_detected``, the all-zero security surface is almost certainly a
|
|
840
|
+
custom-annotation blind spot rather than a truly unsecured API — so the flow
|
|
841
|
+
emits a warning plus a ready-to-paste config hint instead of letting the
|
|
842
|
+
misleading result stand.
|
|
843
|
+
"""
|
|
844
|
+
from sourcecode.security_config import CONFIG_FILENAME
|
|
845
|
+
|
|
846
|
+
t0 = time.monotonic()
|
|
847
|
+
steps: list[str] = []
|
|
848
|
+
quality_warnings: list[str] = []
|
|
849
|
+
output: dict[str, Any] = {}
|
|
850
|
+
|
|
851
|
+
if not _is_java_repo(repo_path):
|
|
852
|
+
quality_warnings.append(
|
|
853
|
+
"not_a_java_repo: spring-audit targets Java/Spring repos and will "
|
|
854
|
+
"report spring_detected=false."
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
# Step 1: Spring semantic audit (TX anomalies + security findings).
|
|
858
|
+
audit = _exec(["spring-audit", repo_path, "--scope", scope])
|
|
859
|
+
steps.append(f"get_spring_audit(scope={scope})")
|
|
860
|
+
if "_exec_error" not in audit:
|
|
861
|
+
output["spring_audit"] = audit
|
|
862
|
+
else:
|
|
863
|
+
quality_warnings.append(f"spring_audit_failed: {audit['_exec_error']}")
|
|
864
|
+
|
|
865
|
+
# Step 2: endpoint authorization surface.
|
|
866
|
+
endpoints = _exec(["endpoints", repo_path])
|
|
867
|
+
steps.append("get_endpoints")
|
|
868
|
+
if "_exec_error" not in endpoints:
|
|
869
|
+
output["endpoint_security_surface"] = endpoints
|
|
870
|
+
else:
|
|
871
|
+
quality_warnings.append(f"endpoints_failed: {endpoints['_exec_error']}")
|
|
872
|
+
|
|
873
|
+
# Config-less blind-spot detection: no sourcecode.config.json + every endpoint
|
|
874
|
+
# none_detected → warn rather than report a misleading 100% unsecured surface.
|
|
875
|
+
has_config = (Path(repo_path) / CONFIG_FILENAME).exists()
|
|
876
|
+
total, none_detected = _endpoint_security_coverage(endpoints)
|
|
877
|
+
if not has_config and total > 0 and none_detected == total:
|
|
878
|
+
quality_warnings.append(
|
|
879
|
+
"config_less_security_blind_spot: no sourcecode.config.json found and "
|
|
880
|
+
f"all {total} endpoints report none_detected. If this repo uses a custom "
|
|
881
|
+
"authorization annotation, the surface is a false negative — add the "
|
|
882
|
+
"config below and re-run."
|
|
883
|
+
)
|
|
884
|
+
output["security_config_hint"] = {
|
|
885
|
+
"reason": "no_custom_security_annotations_configured",
|
|
886
|
+
"file": CONFIG_FILENAME,
|
|
887
|
+
"example": {
|
|
888
|
+
"customSecurityAnnotations": [
|
|
889
|
+
{
|
|
890
|
+
"shortName": "YourSecurityAnnotation",
|
|
891
|
+
"resourceParam": "resourceName",
|
|
892
|
+
"levelParam": "requiredLevel",
|
|
893
|
+
}
|
|
894
|
+
]
|
|
895
|
+
},
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
ttfca_ms = int((time.monotonic() - t0) * 1000)
|
|
899
|
+
|
|
900
|
+
return {
|
|
901
|
+
"session_state": SESSION_READY_FOR_REVIEW,
|
|
902
|
+
"flow": "security_audit",
|
|
903
|
+
"scope": scope,
|
|
904
|
+
"endpoint_security_coverage": {
|
|
905
|
+
"total_endpoints": total,
|
|
906
|
+
"none_detected": none_detected,
|
|
907
|
+
"config_present": has_config,
|
|
908
|
+
},
|
|
909
|
+
"steps_executed": steps,
|
|
910
|
+
"quality_warnings": quality_warnings,
|
|
911
|
+
"consolidated_output": output,
|
|
912
|
+
"session_meta": {
|
|
913
|
+
"ttfca_ms": ttfca_ms,
|
|
914
|
+
"steps_auto_executed": len(steps),
|
|
915
|
+
"tools_suggested_to_agent": 0,
|
|
916
|
+
},
|
|
917
|
+
}
|
sourcecode/mcp/server.py
CHANGED
|
@@ -510,6 +510,75 @@ def run_feature_flow(repo_path: str = ".", feature_description: str = "") -> dic
|
|
|
510
510
|
)
|
|
511
511
|
|
|
512
512
|
|
|
513
|
+
@mcp.tool()
|
|
514
|
+
def run_migrate_flow(repo_path: str = ".", min_severity: str = "low") -> dict:
|
|
515
|
+
"""Migration Flow — Spring Boot 2→3 readiness in one call. JAVA/SPRING ONLY.
|
|
516
|
+
|
|
517
|
+
Primary high-value entry point for migration planning. Wraps migrate-check and
|
|
518
|
+
lifts the headline numbers to the top level so the agent can plan a 2→3 upgrade
|
|
519
|
+
without parsing the full report:
|
|
520
|
+
- readiness_score (0–100; 100 = ready), blocking_count, estimated_effort_days
|
|
521
|
+
- by_severity and by_target breakdown (jakarta / spring_security_6 / java_11)
|
|
522
|
+
|
|
523
|
+
Use this instead of calling get_migration_readiness and interpreting it by hand.
|
|
524
|
+
Returns headline + consolidated_output.migration_readiness (full report).
|
|
525
|
+
|
|
526
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
527
|
+
min_severity: "critical" | "high" | "medium" | "low" (default — include everything).
|
|
528
|
+
"""
|
|
529
|
+
_raw = repo_path
|
|
530
|
+
try:
|
|
531
|
+
if not isinstance(repo_path, str):
|
|
532
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
533
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
534
|
+
_path_err = _check_repo_path(repo_path)
|
|
535
|
+
if _path_err is not None:
|
|
536
|
+
return _path_err
|
|
537
|
+
from sourcecode.mcp.orchestrator import run_migrate_flow_impl
|
|
538
|
+
return _ok(run_migrate_flow_impl(repo_path, min_severity or "low"))
|
|
539
|
+
except Exception as exc:
|
|
540
|
+
return _err(
|
|
541
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
542
|
+
"INTERNAL_ERROR",
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
@mcp.tool()
|
|
547
|
+
def run_security_audit_flow(repo_path: str = ".", scope: str = "all") -> dict:
|
|
548
|
+
"""Security Audit Flow — Spring findings + endpoint authorization surface. JAVA/SPRING ONLY.
|
|
549
|
+
|
|
550
|
+
Auto-chains in one call:
|
|
551
|
+
1. get_spring_audit(scope) — TX anomalies + security-surface findings
|
|
552
|
+
2. get_endpoints — endpoint authorization surface
|
|
553
|
+
|
|
554
|
+
Config-less safeguard: when no sourcecode.config.json is present and every
|
|
555
|
+
endpoint reads none_detected, the flow flags a likely custom-annotation blind
|
|
556
|
+
spot (quality_warnings) and returns a ready-to-paste security_config_hint —
|
|
557
|
+
rather than letting a misleading 100% none_detected surface stand.
|
|
558
|
+
|
|
559
|
+
Returns endpoint_security_coverage + consolidated_output (spring_audit,
|
|
560
|
+
endpoint_security_surface, optional security_config_hint).
|
|
561
|
+
|
|
562
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
563
|
+
scope: "all" (default) | "tx" | "security".
|
|
564
|
+
"""
|
|
565
|
+
_raw = repo_path
|
|
566
|
+
try:
|
|
567
|
+
if not isinstance(repo_path, str):
|
|
568
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
569
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
570
|
+
_path_err = _check_repo_path(repo_path)
|
|
571
|
+
if _path_err is not None:
|
|
572
|
+
return _path_err
|
|
573
|
+
from sourcecode.mcp.orchestrator import run_security_audit_flow_impl
|
|
574
|
+
return _ok(run_security_audit_flow_impl(repo_path, scope or "all"))
|
|
575
|
+
except Exception as exc:
|
|
576
|
+
return _err(
|
|
577
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
578
|
+
"INTERNAL_ERROR",
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
513
582
|
@mcp.tool()
|
|
514
583
|
def get_cold_start_context(repo_path: str = ".") -> dict:
|
|
515
584
|
"""Instant session bootstrap from persisted Repository Intelligence Snapshot (RIS).
|
|
@@ -1333,16 +1402,19 @@ _NATIVE_MCP_TOOLS: frozenset[str] = frozenset({
|
|
|
1333
1402
|
"run_pr_review_flow",
|
|
1334
1403
|
"run_bug_investigation_flow",
|
|
1335
1404
|
"run_feature_flow",
|
|
1405
|
+
"run_migrate_flow",
|
|
1406
|
+
"run_security_audit_flow",
|
|
1336
1407
|
})
|
|
1337
1408
|
|
|
1338
1409
|
|
|
1339
1410
|
def _finalize_mcp_registry() -> None:
|
|
1340
1411
|
"""Sync the MCP server with the runtime-generated registry.
|
|
1341
1412
|
|
|
1342
|
-
Removes every tool except the
|
|
1343
|
-
analyze_task, flow runners
|
|
1344
|
-
module and have no backing CLI command.
|
|
1345
|
-
runtime-derived registry
|
|
1413
|
+
Removes every tool except the native orchestration tools (start_session,
|
|
1414
|
+
analyze_task, and the flow runners — see _NATIVE_MCP_TOOLS) which are
|
|
1415
|
+
registered via @mcp.tool() in this module and have no backing CLI command.
|
|
1416
|
+
All other tools are rebuilt from the runtime-derived registry
|
|
1417
|
+
(build_mcp_tool_specs).
|
|
1346
1418
|
"""
|
|
1347
1419
|
from sourcecode.mcp.registry import build_mcp_tool_specs, make_tool_callable, validate_registry
|
|
1348
1420
|
|
sourcecode/validation_surface.py
CHANGED
|
@@ -302,4 +302,19 @@ def build_validation_surface(
|
|
|
302
302
|
spec_path = endpoints_data.get("openapi_spec")
|
|
303
303
|
if spec_path:
|
|
304
304
|
result["openapi_spec"] = spec_path
|
|
305
|
+
else:
|
|
306
|
+
# No OpenAPI spec on disk / under target/generated-sources. Declarative
|
|
307
|
+
# DTO constraints cannot be recovered, so a sea of zeros here is expected
|
|
308
|
+
# and NOT a sign the repo lacks validation — it just isn't OpenAPI-driven.
|
|
309
|
+
# Surface this explicitly so the result is not silently misread as
|
|
310
|
+
# "no validation anywhere".
|
|
311
|
+
result["openapi_spec"] = None
|
|
312
|
+
result["note"] = (
|
|
313
|
+
"No OpenAPI spec found (no spec on disk or under "
|
|
314
|
+
"target/generated-sources). Declarative DTO constraints cannot be "
|
|
315
|
+
"recovered; only source-declared custom validators are reported. "
|
|
316
|
+
"Body-endpoint and validated-field counts will read zero unless an "
|
|
317
|
+
"OpenAPI spec is present — this is expected, not a missing-validation "
|
|
318
|
+
"finding."
|
|
319
|
+
)
|
|
305
320
|
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.46.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
|
|
@@ -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
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=wdCd8tksQK-HjW2hTdGl_yIjBeUdzZy-xU36D1PCWmo,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=liCwQmLgb5vplohy8arjYxs_HOIv5C9MjLh_gY6bc5Q,44115
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=DEwucOPJguLsVtg5cV8mWXNi112l5jmBhv73KGGebVk,24849
|
|
8
8
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
9
9
|
sourcecode/classifier.py,sha256=hKzg-nQ47htqqIUzSGvYxv15cXrA3KgICTwJmdqal0o,8095
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=ne0Ok-IYVn5-_sg4XLm5CS6jn58z78RezFbc9DOdOFE,257430
|
|
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
|
|
@@ -61,7 +61,7 @@ sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM
|
|
|
61
61
|
sourcecode/spring_tx_analyzer.py,sha256=FdFcyqPp3aT9oJ-PKrnXcTA6s69wdvzG-NBm0GMGPTU,30717
|
|
62
62
|
sourcecode/summarizer.py,sha256=zgdps7yS2IktAbWe7IWz0oUcr3QIuNPRGrsScbZ4R1g,21797
|
|
63
63
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
64
|
-
sourcecode/validation_surface.py,sha256=
|
|
64
|
+
sourcecode/validation_surface.py,sha256=_HiKeUuN8wk8V2pWp1TRcwB_BwxsxyFWTqzCKGx1B8M,12747
|
|
65
65
|
sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
|
|
66
66
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
67
67
|
sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
|
|
@@ -86,10 +86,10 @@ sourcecode/detectors/systems.py,sha256=nYaKbGDFu0EOXFcd_1doWFT3tTUdkbxc2DjHUF5Tc
|
|
|
86
86
|
sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG65hZ4,1693
|
|
87
87
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
88
88
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
89
|
-
sourcecode/mcp/orchestrator.py,sha256=
|
|
89
|
+
sourcecode/mcp/orchestrator.py,sha256=kT7IssYNyQXVtDf2Q69qrMSTuyJlJ1Rhkp6_EHqc_38,36520
|
|
90
90
|
sourcecode/mcp/registry.py,sha256=8LxxalpJy1L_BrEfwiVfywFVOcJJMXLGusJaUGGH7Y4,65663
|
|
91
91
|
sourcecode/mcp/runner.py,sha256=-Dp2qPGRkfNTVen6bKh7WtzQqpcEtsrXoiuajvshlKk,2866
|
|
92
|
-
sourcecode/mcp/server.py,sha256=
|
|
92
|
+
sourcecode/mcp/server.py,sha256=6Q98B1JkgBR5pZx3JyyXZosazpPX7FSBDqfG9nlZaII,63114
|
|
93
93
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
94
94
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
95
95
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -101,8 +101,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
101
101
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
102
102
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
103
103
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
104
|
-
sourcecode-1.
|
|
105
|
-
sourcecode-1.
|
|
106
|
-
sourcecode-1.
|
|
107
|
-
sourcecode-1.
|
|
108
|
-
sourcecode-1.
|
|
104
|
+
sourcecode-1.46.0.dist-info/METADATA,sha256=EVvziqzOlPxygHshUqVQ3DYrYPRDKzQwLf0ewY9twKY,32359
|
|
105
|
+
sourcecode-1.46.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
106
|
+
sourcecode-1.46.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
107
|
+
sourcecode-1.46.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
108
|
+
sourcecode-1.46.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|