sourcecode 1.35.20__py3-none-any.whl → 1.35.22__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 +9 -2
- sourcecode/dependency_analyzer.py +72 -0
- sourcecode/mcp/server.py +119 -8
- sourcecode/repository_ir.py +7 -1
- sourcecode/spring_security_audit.py +14 -7
- sourcecode/spring_tx_analyzer.py +13 -6
- {sourcecode-1.35.20.dist-info → sourcecode-1.35.22.dist-info}/METADATA +1 -1
- {sourcecode-1.35.20.dist-info → sourcecode-1.35.22.dist-info}/RECORD +12 -12
- {sourcecode-1.35.20.dist-info → sourcecode-1.35.22.dist-info}/WHEEL +0 -0
- {sourcecode-1.35.20.dist-info → sourcecode-1.35.22.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.35.20.dist-info → sourcecode-1.35.22.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -3912,7 +3912,8 @@ def spring_audit_cmd(
|
|
|
3912
3912
|
)
|
|
3913
3913
|
raise typer.Exit(code=1)
|
|
3914
3914
|
|
|
3915
|
-
|
|
3915
|
+
_file_limitations: list[str] = []
|
|
3916
|
+
file_list = find_java_files(target, limitations=_file_limitations)
|
|
3916
3917
|
if not file_list:
|
|
3917
3918
|
empty_result = SpringAuditResult(
|
|
3918
3919
|
spring_detected=False,
|
|
@@ -3961,6 +3962,9 @@ def spring_audit_cmd(
|
|
|
3961
3962
|
metadata=merged_meta,
|
|
3962
3963
|
).finalize()
|
|
3963
3964
|
|
|
3965
|
+
if _file_limitations:
|
|
3966
|
+
combined.limitations.extend(_file_limitations)
|
|
3967
|
+
|
|
3964
3968
|
# Populate git_head from repo HEAD — non-fatal.
|
|
3965
3969
|
try:
|
|
3966
3970
|
import subprocess as _sub_sa
|
|
@@ -4088,8 +4092,11 @@ def migrate_check_cmd(
|
|
|
4088
4092
|
)
|
|
4089
4093
|
raise typer.Exit(code=1)
|
|
4090
4094
|
|
|
4091
|
-
|
|
4095
|
+
_file_limitations: list[str] = []
|
|
4096
|
+
file_list = find_java_files(target, limitations=_file_limitations)
|
|
4092
4097
|
report = run_migrate_check(file_list, target, min_severity=min_severity)
|
|
4098
|
+
if _file_limitations:
|
|
4099
|
+
report.limitations.extend(_file_limitations)
|
|
4093
4100
|
|
|
4094
4101
|
if format == "text":
|
|
4095
4102
|
output = report.to_text(min_severity=min_severity)
|
|
@@ -1142,6 +1142,78 @@ class DependencyAnalyzer:
|
|
|
1142
1142
|
records: list[DependencyRecord] = []
|
|
1143
1143
|
deps_elem = root_elem.find(f"{ns}dependencies")
|
|
1144
1144
|
if deps_elem is None:
|
|
1145
|
+
# Multi-module aggregator POM: scan up to 5 declared submodule pom.xml files.
|
|
1146
|
+
modules_elem = root_elem.find(f"{ns}modules")
|
|
1147
|
+
if modules_elem is not None:
|
|
1148
|
+
submodule_records: list[DependencyRecord] = []
|
|
1149
|
+
submodule_limitations: list[str] = []
|
|
1150
|
+
seen_submodule_deps: set[str] = set()
|
|
1151
|
+
_all_modules = list(modules_elem.findall(f"{ns}module"))
|
|
1152
|
+
if len(_all_modules) > 5:
|
|
1153
|
+
submodule_limitations.append(
|
|
1154
|
+
f"MAX_SUBMODULES_REACHED: scanned 5 of {len(_all_modules)} declared Maven submodules"
|
|
1155
|
+
)
|
|
1156
|
+
for mod_elem in _all_modules[:5]:
|
|
1157
|
+
mod_name = (mod_elem.text or "").strip()
|
|
1158
|
+
if not mod_name:
|
|
1159
|
+
continue
|
|
1160
|
+
sub_pom = root / mod_name / "pom.xml"
|
|
1161
|
+
if not sub_pom.exists():
|
|
1162
|
+
continue
|
|
1163
|
+
try:
|
|
1164
|
+
sub_tree = ET.parse(sub_pom)
|
|
1165
|
+
except (ET.ParseError, OSError):
|
|
1166
|
+
continue
|
|
1167
|
+
sub_root = sub_tree.getroot()
|
|
1168
|
+
sub_ns_match = re.match(r"\{[^}]+\}", sub_root.tag)
|
|
1169
|
+
sub_ns = sub_ns_match.group(0) if sub_ns_match else ""
|
|
1170
|
+
sub_props = self._parse_maven_properties(sub_root, sub_ns)
|
|
1171
|
+
sub_dm = self._parse_dependency_management(sub_root, sub_ns, sub_props)
|
|
1172
|
+
# Inherit parent properties for version resolution
|
|
1173
|
+
for k, v in properties.items():
|
|
1174
|
+
sub_props.setdefault(k, v)
|
|
1175
|
+
for k, v in dm_versions.items():
|
|
1176
|
+
sub_dm.setdefault(k, v)
|
|
1177
|
+
sub_deps_elem = sub_root.find(f"{sub_ns}dependencies")
|
|
1178
|
+
if sub_deps_elem is None:
|
|
1179
|
+
continue
|
|
1180
|
+
for dep in sub_deps_elem.findall(f"{sub_ns}dependency"):
|
|
1181
|
+
gid = (dep.findtext(f"{sub_ns}groupId") or "").strip()
|
|
1182
|
+
aid = (dep.findtext(f"{sub_ns}artifactId") or "").strip()
|
|
1183
|
+
if not gid or not aid:
|
|
1184
|
+
continue
|
|
1185
|
+
dep_key = f"{gid}:{aid}"
|
|
1186
|
+
if dep_key in seen_submodule_deps:
|
|
1187
|
+
continue
|
|
1188
|
+
seen_submodule_deps.add(dep_key)
|
|
1189
|
+
ver_raw = (dep.findtext(f"{sub_ns}version") or "").strip() or None
|
|
1190
|
+
declared = self._resolve_maven_version(ver_raw, sub_props)
|
|
1191
|
+
if declared is None:
|
|
1192
|
+
declared = sub_dm.get(dep_key)
|
|
1193
|
+
scope_text = (dep.findtext(f"{sub_ns}scope") or "compile").strip().lower()
|
|
1194
|
+
if scope_text == "test":
|
|
1195
|
+
scope = "dev"
|
|
1196
|
+
elif scope_text == "provided":
|
|
1197
|
+
scope = "provided"
|
|
1198
|
+
else:
|
|
1199
|
+
scope = "direct"
|
|
1200
|
+
resolved_version = None
|
|
1201
|
+
if declared is None and parent_version:
|
|
1202
|
+
if gid == "org.springframework.boot":
|
|
1203
|
+
resolved_version = parent_version
|
|
1204
|
+
elif gid == "org.springframework.security" and "spring-security.version" in sub_props:
|
|
1205
|
+
resolved_version = sub_props["spring-security.version"]
|
|
1206
|
+
submodule_records.append(DependencyRecord(
|
|
1207
|
+
name=dep_key,
|
|
1208
|
+
ecosystem="java",
|
|
1209
|
+
scope=scope,
|
|
1210
|
+
declared_version=declared,
|
|
1211
|
+
resolved_version=resolved_version,
|
|
1212
|
+
source="manifest",
|
|
1213
|
+
manifest_path=f"{mod_name}/pom.xml",
|
|
1214
|
+
))
|
|
1215
|
+
if submodule_records:
|
|
1216
|
+
return submodule_records, submodule_limitations
|
|
1145
1217
|
return [], ["java: pom.xml has no <dependencies> block"]
|
|
1146
1218
|
|
|
1147
1219
|
for dep in deps_elem.findall(f"{ns}dependency"):
|
sourcecode/mcp/server.py
CHANGED
|
@@ -192,7 +192,48 @@ def _execute(args: list[str]) -> dict | CallToolResult:
|
|
|
192
192
|
return _ok(result)
|
|
193
193
|
|
|
194
194
|
|
|
195
|
+
# Per-tool character budgets (4 chars ≈ 1 token; keeps Cursor under its 10k token limit).
|
|
196
|
+
# List fields are trimmed front-to-back until output fits.
|
|
197
|
+
_MCP_CHAR_BUDGETS: dict[str, int] = {
|
|
198
|
+
"get_agent_context": 38_000,
|
|
199
|
+
"get_spring_audit": 38_000,
|
|
200
|
+
"get_migration_readiness": 38_000,
|
|
201
|
+
}
|
|
202
|
+
_MCP_TRIM_FIELDS = (
|
|
203
|
+
"findings", "relevant_files", "key_dependencies",
|
|
204
|
+
"entry_points", "limitations", "gaps", "code_notes",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _cap_mcp_output(tool_name: str, data: Any) -> Any:
|
|
209
|
+
"""Trim list fields in data until JSON serialisation fits within the tool's char budget."""
|
|
210
|
+
budget = _MCP_CHAR_BUDGETS.get(tool_name)
|
|
211
|
+
if not budget or not isinstance(data, dict):
|
|
212
|
+
return data
|
|
213
|
+
if len(json.dumps(data, default=str)) <= budget:
|
|
214
|
+
return data
|
|
215
|
+
result = dict(data)
|
|
216
|
+
for field in _MCP_TRIM_FIELDS:
|
|
217
|
+
if field not in result or not isinstance(result[field], list):
|
|
218
|
+
continue
|
|
219
|
+
items = list(result[field])
|
|
220
|
+
while items and len(json.dumps(result, default=str)) > budget:
|
|
221
|
+
items = items[:-1]
|
|
222
|
+
result[field] = items
|
|
223
|
+
if len(json.dumps(result, default=str)) <= budget:
|
|
224
|
+
break
|
|
225
|
+
if len(json.dumps(result, default=str)) > budget:
|
|
226
|
+
result["_mcp_truncated"] = True
|
|
227
|
+
result["_mcp_truncation_note"] = (
|
|
228
|
+
f"Output capped at ~{budget // 4}k tokens for MCP compatibility. "
|
|
229
|
+
"Use CLI directly for full output."
|
|
230
|
+
)
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
|
|
195
234
|
_DEFAULT_TESTS_TIMEOUT_MS = 15_000
|
|
235
|
+
_DEFAULT_SPRING_AUDIT_TIMEOUT_MS = 120_000 # 2 min
|
|
236
|
+
_DEFAULT_IMPACT_TIMEOUT_MS = 60_000 # 1 min
|
|
196
237
|
|
|
197
238
|
# Regex for MINGW paths: /c/some/path → C:/some/path
|
|
198
239
|
_MINGW_PATH_RE = re.compile(r"^/([a-zA-Z])(/.*)?$")
|
|
@@ -568,7 +609,10 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
568
609
|
args = [repo_path, "--agent"]
|
|
569
610
|
if git_context:
|
|
570
611
|
args.append("--git-context")
|
|
571
|
-
|
|
612
|
+
result = _execute(args)
|
|
613
|
+
if isinstance(result, dict) and result.get("success"):
|
|
614
|
+
result = _ok(_cap_mcp_output("get_agent_context", result.get("data")))
|
|
615
|
+
return result
|
|
572
616
|
except Exception as exc:
|
|
573
617
|
return _err(
|
|
574
618
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -641,7 +685,26 @@ def get_spring_audit(repo_path: str = ".", scope: str = "all") -> dict:
|
|
|
641
685
|
_path_err = _check_repo_path(repo_path)
|
|
642
686
|
if _path_err is not None:
|
|
643
687
|
return _path_err
|
|
644
|
-
|
|
688
|
+
timeout_ms = int(os.environ.get("SOURCECODE_SPRING_AUDIT_TIMEOUT_MS", str(_DEFAULT_SPRING_AUDIT_TIMEOUT_MS)))
|
|
689
|
+
timeout_s = timeout_ms / 1000.0
|
|
690
|
+
_exec = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
691
|
+
try:
|
|
692
|
+
_fut = _exec.submit(_execute, ["spring-audit", repo_path, "--scope", scope])
|
|
693
|
+
_done, _pending = concurrent.futures.wait([_fut], timeout=timeout_s)
|
|
694
|
+
if _pending:
|
|
695
|
+
_exec.shutdown(wait=False)
|
|
696
|
+
return _ok({
|
|
697
|
+
"truncated": True,
|
|
698
|
+
"truncated_reason": f"timeout_{timeout_s:.0f}s",
|
|
699
|
+
"analysis": "timed out — repository may be too large for MCP transport",
|
|
700
|
+
"suggestion": f"Increase SOURCECODE_SPRING_AUDIT_TIMEOUT_MS (current: {timeout_ms}ms) or run via CLI directly",
|
|
701
|
+
})
|
|
702
|
+
result = _fut.result()
|
|
703
|
+
finally:
|
|
704
|
+
_exec.shutdown(wait=True)
|
|
705
|
+
if isinstance(result, dict) and result.get("success"):
|
|
706
|
+
result = _ok(_cap_mcp_output("get_spring_audit", result.get("data")))
|
|
707
|
+
return result
|
|
645
708
|
except Exception as exc:
|
|
646
709
|
return _err(
|
|
647
710
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -691,7 +754,10 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
|
|
|
691
754
|
_path_err = _check_repo_path(repo_path)
|
|
692
755
|
if _path_err is not None:
|
|
693
756
|
return _path_err
|
|
694
|
-
|
|
757
|
+
result = _execute(["migrate-check", repo_path, "--min-severity", min_severity])
|
|
758
|
+
if isinstance(result, dict) and result.get("success"):
|
|
759
|
+
result = _ok(_cap_mcp_output("get_migration_readiness", result.get("data")))
|
|
760
|
+
return result
|
|
695
761
|
except Exception as exc:
|
|
696
762
|
return _err(
|
|
697
763
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -731,7 +797,23 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
|
|
|
731
797
|
if _path_err is not None:
|
|
732
798
|
return _path_err
|
|
733
799
|
args = ["impact-chain", symbol.strip(), repo_path, "--depth", str(depth)]
|
|
734
|
-
|
|
800
|
+
timeout_ms = int(os.environ.get("SOURCECODE_IMPACT_TIMEOUT_MS", str(_DEFAULT_IMPACT_TIMEOUT_MS)))
|
|
801
|
+
timeout_s = timeout_ms / 1000.0
|
|
802
|
+
_exec = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
803
|
+
try:
|
|
804
|
+
_fut = _exec.submit(_execute, args)
|
|
805
|
+
_done, _pending = concurrent.futures.wait([_fut], timeout=timeout_s)
|
|
806
|
+
if _pending:
|
|
807
|
+
_exec.shutdown(wait=False)
|
|
808
|
+
return _ok({
|
|
809
|
+
"truncated": True,
|
|
810
|
+
"truncated_reason": f"timeout_{timeout_s:.0f}s",
|
|
811
|
+
"analysis": "timed out — repository may be too large for MCP transport",
|
|
812
|
+
"suggestion": f"Increase SOURCECODE_IMPACT_TIMEOUT_MS (current: {timeout_ms}ms) or run via CLI directly",
|
|
813
|
+
})
|
|
814
|
+
return _fut.result()
|
|
815
|
+
finally:
|
|
816
|
+
_exec.shutdown(wait=True)
|
|
735
817
|
except Exception as exc:
|
|
736
818
|
return _err(
|
|
737
819
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -1148,7 +1230,23 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
1148
1230
|
if _path_err is not None:
|
|
1149
1231
|
return _path_err
|
|
1150
1232
|
args = ["impact", target.strip(), repo_path, "--depth", str(depth)]
|
|
1151
|
-
|
|
1233
|
+
timeout_ms = int(os.environ.get("SOURCECODE_IMPACT_TIMEOUT_MS", str(_DEFAULT_IMPACT_TIMEOUT_MS)))
|
|
1234
|
+
timeout_s = timeout_ms / 1000.0
|
|
1235
|
+
_exec = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
1236
|
+
try:
|
|
1237
|
+
_fut = _exec.submit(_execute, args)
|
|
1238
|
+
_done, _pending = concurrent.futures.wait([_fut], timeout=timeout_s)
|
|
1239
|
+
if _pending:
|
|
1240
|
+
_exec.shutdown(wait=False)
|
|
1241
|
+
return _ok({
|
|
1242
|
+
"truncated": True,
|
|
1243
|
+
"truncated_reason": f"timeout_{timeout_s:.0f}s",
|
|
1244
|
+
"analysis": "timed out — repository may be too large for MCP transport",
|
|
1245
|
+
"suggestion": f"Increase SOURCECODE_IMPACT_TIMEOUT_MS (current: {timeout_ms}ms) or run via CLI directly",
|
|
1246
|
+
})
|
|
1247
|
+
return _fut.result()
|
|
1248
|
+
finally:
|
|
1249
|
+
_exec.shutdown(wait=True)
|
|
1152
1250
|
except Exception as exc:
|
|
1153
1251
|
return _err(
|
|
1154
1252
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -1269,9 +1367,22 @@ def _finalize_mcp_registry() -> None:
|
|
|
1269
1367
|
structured_output=False,
|
|
1270
1368
|
)
|
|
1271
1369
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1370
|
+
try:
|
|
1371
|
+
drift = validate_registry()
|
|
1372
|
+
if drift:
|
|
1373
|
+
import warnings
|
|
1374
|
+
warnings.warn(
|
|
1375
|
+
f"MCP registry drift detected — server running with potential tool mismatch: {drift}",
|
|
1376
|
+
RuntimeWarning,
|
|
1377
|
+
stacklevel=2,
|
|
1378
|
+
)
|
|
1379
|
+
except Exception as _reg_exc:
|
|
1380
|
+
import warnings
|
|
1381
|
+
warnings.warn(
|
|
1382
|
+
f"MCP registry validation failed — server starting in degraded mode: {_reg_exc}",
|
|
1383
|
+
RuntimeWarning,
|
|
1384
|
+
stacklevel=2,
|
|
1385
|
+
)
|
|
1275
1386
|
|
|
1276
1387
|
|
|
1277
1388
|
_finalize_mcp_registry()
|
sourcecode/repository_ir.py
CHANGED
|
@@ -3312,11 +3312,13 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
3312
3312
|
}
|
|
3313
3313
|
|
|
3314
3314
|
|
|
3315
|
-
def find_java_files(root: Path, *, max_files: int = 8000) -> list[str]:
|
|
3315
|
+
def find_java_files(root: Path, *, max_files: int = 8000, limitations: list[str] | None = None) -> list[str]:
|
|
3316
3316
|
"""Return relative paths to Java files under root, excluding test dirs and vendor."""
|
|
3317
3317
|
results: list[str] = []
|
|
3318
|
+
_capped = False
|
|
3318
3319
|
for p in sorted(root.rglob("*.java")):
|
|
3319
3320
|
if len(results) >= max_files:
|
|
3321
|
+
_capped = True
|
|
3320
3322
|
break
|
|
3321
3323
|
try:
|
|
3322
3324
|
rel = str(p.relative_to(root)).replace("\\", "/")
|
|
@@ -3334,6 +3336,10 @@ def find_java_files(root: Path, *, max_files: int = 8000) -> list[str]:
|
|
|
3334
3336
|
if any(f in rel for f in ("/admin-client/", "/rest-client/", "/client-api/", "/api-client/")):
|
|
3335
3337
|
continue
|
|
3336
3338
|
results.append(rel)
|
|
3339
|
+
if _capped and limitations is not None:
|
|
3340
|
+
limitations.append(
|
|
3341
|
+
f"MAX_JAVA_FILES_REACHED: scanned {max_files} files — repository likely has more"
|
|
3342
|
+
)
|
|
3337
3343
|
return results
|
|
3338
3344
|
|
|
3339
3345
|
|
|
@@ -418,12 +418,15 @@ class SecurityScanner:
|
|
|
418
418
|
model: Optional[SpringSemanticModel] = None,
|
|
419
419
|
) -> list[SpringFinding]:
|
|
420
420
|
all_findings: list[SpringFinding] = []
|
|
421
|
+
self._last_analysis_errors: list[str] = []
|
|
421
422
|
for pattern in self.patterns:
|
|
422
423
|
try:
|
|
423
424
|
found = _call_pattern_analyze(pattern, cir, tx_index, root, model)
|
|
424
425
|
all_findings.extend(found)
|
|
425
|
-
except Exception:
|
|
426
|
-
|
|
426
|
+
except Exception as exc:
|
|
427
|
+
self._last_analysis_errors.append(
|
|
428
|
+
f"{pattern.pattern_id}: {type(exc).__name__}: {exc}"
|
|
429
|
+
)
|
|
427
430
|
deduped = deduplicate_findings(all_findings)
|
|
428
431
|
return sorted(deduped, key=lambda f: (SEVERITY_ORDER.get(f.severity, 9), f.symbol))
|
|
429
432
|
|
|
@@ -475,16 +478,20 @@ def run_security_audit(
|
|
|
475
478
|
or cir.metadata.get("security_model", "unknown") != "unknown"
|
|
476
479
|
)
|
|
477
480
|
|
|
481
|
+
_sec_limitations = [
|
|
482
|
+
"SEC-001: only emitted for annotation_based security model",
|
|
483
|
+
"SEC-002: generic type detection is regex-based on extends edge signatures",
|
|
484
|
+
"SEC-003: only detects controllers visible via cir.endpoints",
|
|
485
|
+
]
|
|
486
|
+
for _err in getattr(scanner, "_last_analysis_errors", []):
|
|
487
|
+
_sec_limitations.append(f"PATTERN_ERROR: {_err}")
|
|
488
|
+
|
|
478
489
|
result = SpringAuditResult(
|
|
479
490
|
repo_id=getattr(cir, "cir_hash", "")[:16],
|
|
480
491
|
spring_detected=_spring_detected,
|
|
481
492
|
scope="security",
|
|
482
493
|
findings=findings,
|
|
483
|
-
limitations=
|
|
484
|
-
"SEC-001: only emitted for annotation_based security model",
|
|
485
|
-
"SEC-002: generic type detection is regex-based on extends edge signatures",
|
|
486
|
-
"SEC-003: only detects controllers visible via cir.endpoints",
|
|
487
|
-
],
|
|
494
|
+
limitations=_sec_limitations,
|
|
488
495
|
metadata={
|
|
489
496
|
"endpoints_analyzed": len(cir.endpoints),
|
|
490
497
|
"security_model": cir.metadata.get("security_model", "unknown"),
|
sourcecode/spring_tx_analyzer.py
CHANGED
|
@@ -672,12 +672,15 @@ class TxPatternEngine:
|
|
|
672
672
|
model: Optional[SpringSemanticModel] = None,
|
|
673
673
|
) -> list[SpringFinding]:
|
|
674
674
|
all_findings: list[SpringFinding] = []
|
|
675
|
+
self._last_analysis_errors: list[str] = []
|
|
675
676
|
for pattern in self.patterns:
|
|
676
677
|
try:
|
|
677
678
|
found = _call_pattern_analyze(pattern, cir, tx_index, root, model)
|
|
678
679
|
all_findings.extend(found)
|
|
679
|
-
except Exception:
|
|
680
|
-
|
|
680
|
+
except Exception as exc:
|
|
681
|
+
self._last_analysis_errors.append(
|
|
682
|
+
f"{pattern.pattern_id}: {type(exc).__name__}: {exc}"
|
|
683
|
+
)
|
|
681
684
|
deduped = deduplicate_findings(all_findings)
|
|
682
685
|
return sorted(deduped, key=lambda f: (SEVERITY_ORDER.get(f.severity, 9), f.symbol))
|
|
683
686
|
|
|
@@ -721,15 +724,19 @@ def run_tx_audit(
|
|
|
721
724
|
|
|
722
725
|
_spring_detected = tx_index.stats()["total"] > 0 or bool(model.bean_graph.beans)
|
|
723
726
|
|
|
727
|
+
_tx_limitations = [
|
|
728
|
+
"Self-invocation via this.method() not detected — requires AST-level analysis",
|
|
729
|
+
"Dynamic dispatch (interface/polymorphic calls) may produce incomplete call chains",
|
|
730
|
+
]
|
|
731
|
+
for _err in getattr(engine, "_last_analysis_errors", []):
|
|
732
|
+
_tx_limitations.append(f"PATTERN_ERROR: {_err}")
|
|
733
|
+
|
|
724
734
|
result = SpringAuditResult(
|
|
725
735
|
repo_id=getattr(cir, "cir_hash", "")[:16],
|
|
726
736
|
spring_detected=_spring_detected,
|
|
727
737
|
scope="tx",
|
|
728
738
|
findings=findings,
|
|
729
|
-
limitations=
|
|
730
|
-
"Self-invocation via this.method() not detected — requires AST-level analysis",
|
|
731
|
-
"Dynamic dispatch (interface/polymorphic calls) may produce incomplete call chains",
|
|
732
|
-
],
|
|
739
|
+
limitations=_tx_limitations,
|
|
733
740
|
metadata={
|
|
734
741
|
"symbols_analyzed": len(getattr(cir, "symbols", [])),
|
|
735
742
|
"tx_boundaries_found": tx_index.stats()["total"],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=p3pMcJ60xxF18F5C5hvsyn5B4uh1rOmKBO_RC94DPAA,104
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=wAyPrXN5DqiGivnMpeEuun2xHDKfBer2_oBsh6kj_vc,30447
|
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=uwpwCnJxMh_eiIVg4cOLv7-aZthvmDFcG4azCOycLkw,24281
|
|
8
8
|
sourcecode/cir_graphs.py,sha256=rZi8JV4ZrAa2WSCeyNa4JIEKQ_yZzDZTsrvVz2KfuKA,8919
|
|
9
9
|
sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=wy5-T6Ba_hvqJoFkSROCJxOpsy_VzbYI98TlJIEeGW0,238063
|
|
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
|
|
@@ -15,7 +15,7 @@ sourcecode/context_summarizer.py,sha256=zlbm8ytdvJToohU108-dwBmEl52xl0gXpf6PZBOW
|
|
|
15
15
|
sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
|
|
16
16
|
sourcecode/contract_pipeline.py,sha256=gvTdDniedm_mjq4vaHqnBY2UkQ0s00gtXqzTLILNXHc,28719
|
|
17
17
|
sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
|
|
18
|
-
sourcecode/dependency_analyzer.py,sha256=
|
|
18
|
+
sourcecode/dependency_analyzer.py,sha256=gvFJf9gHyUGRia3tdPz8s0aX2Re6aohMhb40uFEbjp0,60420
|
|
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
|
|
@@ -40,7 +40,7 @@ sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,
|
|
|
40
40
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
41
41
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
42
42
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
43
|
-
sourcecode/repository_ir.py,sha256=
|
|
43
|
+
sourcecode/repository_ir.py,sha256=5meIvsVN4WlllStxIglZznILKcI0Q2t2XMLB9aVT3uc,169561
|
|
44
44
|
sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
|
|
45
45
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
46
46
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
@@ -51,9 +51,9 @@ sourcecode/spring_event_topology.py,sha256=LvGv5RXtU_O-fVB_OO9eDD2UmZM72Jn2oUHgO
|
|
|
51
51
|
sourcecode/spring_findings.py,sha256=8V91iHOg9hFgg6tLLl4FSsgrF-dBqOcO2s-K5sD_goA,5417
|
|
52
52
|
sourcecode/spring_impact.py,sha256=Ohm2k3W4Wts8Kx8Z7DIM-J-cwGtTJBWKFBsX-WkupBQ,32943
|
|
53
53
|
sourcecode/spring_model.py,sha256=IzMcM5ftw1_EHG3FGUDT7qdAMpo3eqbAE1LRuasfr_4,14739
|
|
54
|
-
sourcecode/spring_security_audit.py,sha256=
|
|
54
|
+
sourcecode/spring_security_audit.py,sha256=AmUkqoExkNZ3YxxZf9TwkwX-f7P_SETm0QC7VqEAqh4,20618
|
|
55
55
|
sourcecode/spring_semantic.py,sha256=CiAf77p48-RFrUF0zbgww4w2Xigrbo1t5M3ZCDIfV_g,12032
|
|
56
|
-
sourcecode/spring_tx_analyzer.py,sha256=
|
|
56
|
+
sourcecode/spring_tx_analyzer.py,sha256=u4_ckdEFZUiIsHdUX4OaIhnvoTdAwrxNTFweG6vc7wE,30526
|
|
57
57
|
sourcecode/summarizer.py,sha256=YspHEVeYJVmltq0FMtGZF8kIP3qiR2KLcanGL6Y7uTI,20747
|
|
58
58
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
59
59
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
@@ -82,7 +82,7 @@ sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,17
|
|
|
82
82
|
sourcecode/mcp/orchestrator.py,sha256=BMi1D6liJHI3DXiaC8yeBLLP0wXajpCP3-vnRGqrvnw,26850
|
|
83
83
|
sourcecode/mcp/registry.py,sha256=XeshSuT6NMmeUZ2GCzNVcKcr-2Ljoj4qO-lvSrg17EM,63135
|
|
84
84
|
sourcecode/mcp/runner.py,sha256=-Dp2qPGRkfNTVen6bKh7WtzQqpcEtsrXoiuajvshlKk,2866
|
|
85
|
-
sourcecode/mcp/server.py,sha256=
|
|
85
|
+
sourcecode/mcp/server.py,sha256=Zapr4lY0i4tqSXY2BfA283VzStHTohNr9N0uMnRSIIA,59911
|
|
86
86
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
87
87
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
88
88
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -94,8 +94,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
94
94
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
95
95
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
96
96
|
sourcecode/telemetry/transport.py,sha256=QSslxIwij8YkRWcVvxykODDrkiN_GAAEu3dUP7KIWeE,1651
|
|
97
|
-
sourcecode-1.35.
|
|
98
|
-
sourcecode-1.35.
|
|
99
|
-
sourcecode-1.35.
|
|
100
|
-
sourcecode-1.35.
|
|
101
|
-
sourcecode-1.35.
|
|
97
|
+
sourcecode-1.35.22.dist-info/METADATA,sha256=myCZutK9p4r8d-O38SYo4et7pkpwASrkuJOnr_WIAac,21297
|
|
98
|
+
sourcecode-1.35.22.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
99
|
+
sourcecode-1.35.22.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
100
|
+
sourcecode-1.35.22.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
101
|
+
sourcecode-1.35.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|