sourcecode 2.0.1__py3-none-any.whl → 2.2.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 +6 -2
- sourcecode/caller_metrics.py +42 -0
- sourcecode/cli.py +325 -207
- sourcecode/detectors/java.py +29 -1
- sourcecode/endpoint_metrics.py +42 -0
- sourcecode/explain.py +2 -0
- sourcecode/license.py +31 -13
- sourcecode/mcp/onboarding/applier.py +14 -6
- sourcecode/mcp/registry.py +22 -22
- sourcecode/mcp/runner.py +2 -2
- sourcecode/mcp/server.py +24 -24
- sourcecode/mcp_nudge.py +1 -1
- sourcecode/path_filters.py +2 -1
- sourcecode/repository_ir.py +51 -15
- sourcecode/ris.py +1 -1
- sourcecode/security_posture.py +608 -0
- sourcecode/semantic_integration_engine.py +18 -2
- sourcecode/serializer.py +24 -4
- sourcecode/spring_findings.py +4 -0
- sourcecode/spring_security_audit.py +31 -0
- sourcecode/spring_semantic.py +18 -5
- sourcecode/spring_tx_analyzer.py +21 -6
- sourcecode/telemetry/consent.py +1 -1
- sourcecode/validation_inference.py +249 -0
- sourcecode-2.2.0.dist-info/METADATA +241 -0
- {sourcecode-2.0.1.dist-info → sourcecode-2.2.0.dist-info}/RECORD +29 -25
- {sourcecode-2.0.1.dist-info → sourcecode-2.2.0.dist-info}/entry_points.txt +1 -0
- sourcecode-2.0.1.dist-info/METADATA +0 -922
- {sourcecode-2.0.1.dist-info → sourcecode-2.2.0.dist-info}/WHEEL +0 -0
- {sourcecode-2.0.1.dist-info → sourcecode-2.2.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/mcp/server.py
CHANGED
|
@@ -108,14 +108,14 @@ def _coerce_cli_error(exc: Exception, default_message: str) -> CallToolResult:
|
|
|
108
108
|
and "pro" in payload.get("error", "").lower()
|
|
109
109
|
):
|
|
110
110
|
feature = payload.get("feature", "")
|
|
111
|
-
msg = payload.get("message", f"'{feature}' requires a Pro license. Run:
|
|
111
|
+
msg = payload.get("message", f"'{feature}' requires a Pro license. Run: ask activate <key>")
|
|
112
112
|
structured = {
|
|
113
113
|
"success": False,
|
|
114
114
|
"data": None,
|
|
115
115
|
"error": build_error_object(
|
|
116
116
|
"PRO_REQUIRED",
|
|
117
117
|
msg,
|
|
118
|
-
hint="
|
|
118
|
+
hint="ask activate <license_key>",
|
|
119
119
|
expected="Active Pro license.",
|
|
120
120
|
),
|
|
121
121
|
}
|
|
@@ -627,7 +627,7 @@ def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict
|
|
|
627
627
|
Includes security_surface, mybatis, and transactional_boundaries for Java/Spring projects.
|
|
628
628
|
For richer machine-oriented detail (deeper signals, more sections), use get_agent_context.
|
|
629
629
|
|
|
630
|
-
Maps to:
|
|
630
|
+
Maps to: ask <repo_path> --compact [--git-context]
|
|
631
631
|
repo_path: absolute path to the repository (default: current working directory).
|
|
632
632
|
git_context: include git log and branch context in the analysis.
|
|
633
633
|
"""
|
|
@@ -661,7 +661,7 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
661
661
|
architecture layers, security surface, transactional boundaries, module graph summary.
|
|
662
662
|
Prefer get_compact_context for quick orientation or token-constrained workflows.
|
|
663
663
|
|
|
664
|
-
Maps to:
|
|
664
|
+
Maps to: ask <repo_path> --agent [--git-context]
|
|
665
665
|
repo_path: absolute path to the repository (default: current working directory).
|
|
666
666
|
git_context: include git log and branch context in the analysis.
|
|
667
667
|
"""
|
|
@@ -696,7 +696,7 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
696
696
|
Do NOT call this on non-Java repositories — it will return empty results.
|
|
697
697
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
698
698
|
|
|
699
|
-
Maps to:
|
|
699
|
+
Maps to: ask endpoints <repo_path>
|
|
700
700
|
Returns: endpoints list with method, path, controller, handler fields;
|
|
701
701
|
security dict always present (policy: roles_allowed|permit_all|deny_all|
|
|
702
702
|
authenticated|...|none_detected); none_detected = no auth annotation found.
|
|
@@ -733,7 +733,7 @@ def get_spring_audit(repo_path: str = ".", scope: str = "all") -> dict:
|
|
|
733
733
|
|
|
734
734
|
Do NOT call this on non-Java repositories — it will return spring_detected=false.
|
|
735
735
|
|
|
736
|
-
Maps to:
|
|
736
|
+
Maps to: ask spring-audit <repo_path> --scope <scope>
|
|
737
737
|
Returns: SpringAuditResult with schema_version, spring_detected, scope, summary,
|
|
738
738
|
findings list (id, pattern_id, category, severity, confidence, title,
|
|
739
739
|
symbol, source_file, evidence, explanation, fix_hint), limitations, metadata.
|
|
@@ -790,7 +790,7 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
|
|
|
790
790
|
the goal is migration planning — not ongoing audit.
|
|
791
791
|
Do NOT call on non-Java repositories — returns readiness_score=null (N/A) with no findings.
|
|
792
792
|
|
|
793
|
-
Maps to:
|
|
793
|
+
Maps to: ask migrate-check <repo_path> --min-severity <min_severity>
|
|
794
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),
|
|
@@ -847,7 +847,7 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
|
|
|
847
847
|
|
|
848
848
|
Do NOT call this on non-Java repositories — it will return resolution=not_found.
|
|
849
849
|
|
|
850
|
-
Maps to:
|
|
850
|
+
Maps to: ask impact-chain <symbol> <repo_path> [--depth <depth>]
|
|
851
851
|
Returns: ImpactChainResult with schema_version, symbol, resolution,
|
|
852
852
|
direct_callers, indirect_callers, endpoints_affected,
|
|
853
853
|
transaction_boundary (propagation/isolation/read_only),
|
|
@@ -901,7 +901,7 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
|
|
|
901
901
|
def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
902
902
|
"""Compact analysis of a specific module or subdirectory within a repository.
|
|
903
903
|
|
|
904
|
-
Maps to:
|
|
904
|
+
Maps to: ask <repo_path>/<module> --compact
|
|
905
905
|
repo_path: absolute path to the repository root.
|
|
906
906
|
module: subdirectory name relative to repo_path (e.g. 'src/auth', 'api', 'core').
|
|
907
907
|
"""
|
|
@@ -944,7 +944,7 @@ def _auto_since(repo_path: str) -> str:
|
|
|
944
944
|
def get_delta(repo_path: str = ".", since: str = "") -> dict:
|
|
945
945
|
"""Incremental context: git-changed files since a reference commit.
|
|
946
946
|
|
|
947
|
-
Maps to:
|
|
947
|
+
Maps to: ask prepare-context delta <repo_path> --since <since>
|
|
948
948
|
repo_path: absolute path to the repository (default: current working directory).
|
|
949
949
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
950
950
|
If empty or omitted, auto-detects merge-base with origin/main (or
|
|
@@ -1055,12 +1055,12 @@ def check_freshness(repo_path: str = ".") -> dict:
|
|
|
1055
1055
|
def get_ir_summary(repo_path: str = ".") -> dict:
|
|
1056
1056
|
"""Deterministic symbol-level IR summary for Java repositories. Java only.
|
|
1057
1057
|
|
|
1058
|
-
Maps to:
|
|
1058
|
+
Maps to: ask repo-ir <repo_path> --summary-only
|
|
1059
1059
|
Returns: reverse_graph (top 10 hubs), route_surface (top 50 endpoints),
|
|
1060
1060
|
subsystems (top 15), impact, analysis. Full graph nodes/edges omitted.
|
|
1061
1061
|
|
|
1062
1062
|
Output is bounded to ~100 KB for LLM safety. For full IR (can exceed 10 MB
|
|
1063
|
-
on large repos), use the CLI:
|
|
1063
|
+
on large repos), use the CLI: ask repo-ir <path> --output ir.json
|
|
1064
1064
|
Use get_compact_context or get_agent_context for non-Java repos.
|
|
1065
1065
|
|
|
1066
1066
|
IR access paths (for full IR via CLI):
|
|
@@ -1097,7 +1097,7 @@ def get_ir_summary(repo_path: str = ".") -> dict:
|
|
|
1097
1097
|
def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
1098
1098
|
"""Risk-ranked files for bug investigation, optionally focused by symptom.
|
|
1099
1099
|
|
|
1100
|
-
Maps to:
|
|
1100
|
+
Maps to: ask prepare-context fix-bug <repo_path> [--symptom <symptom>]
|
|
1101
1101
|
Includes compact_base: security_surface, transactional_boundaries, spring_profiles.
|
|
1102
1102
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1103
1103
|
symptom: optional error message or class name to focus the file ranking
|
|
@@ -1126,7 +1126,7 @@ def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
|
1126
1126
|
def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
1127
1127
|
"""Execution paths and risk analysis for changed files in a pull request.
|
|
1128
1128
|
|
|
1129
|
-
Maps to:
|
|
1129
|
+
Maps to: ask prepare-context review-pr <repo_path> [--since <since>]
|
|
1130
1130
|
Returns: compact_base + execution_paths (diff-scoped) + hotspots for changed files.
|
|
1131
1131
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1132
1132
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
@@ -1155,7 +1155,7 @@ def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
|
1155
1155
|
def onboard_context(repo_path: str = ".") -> dict:
|
|
1156
1156
|
"""Onboarding context: structured overview for new contributors.
|
|
1157
1157
|
|
|
1158
|
-
Maps to:
|
|
1158
|
+
Maps to: ask prepare-context onboard <repo_path>
|
|
1159
1159
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1160
1160
|
"""
|
|
1161
1161
|
_raw = repo_path
|
|
@@ -1178,7 +1178,7 @@ def onboard_context(repo_path: str = ".") -> dict:
|
|
|
1178
1178
|
def explain_context(repo_path: str = ".") -> dict:
|
|
1179
1179
|
"""Architecture and entry-point explanation for a repository.
|
|
1180
1180
|
|
|
1181
|
-
Maps to:
|
|
1181
|
+
Maps to: ask prepare-context explain <repo_path>
|
|
1182
1182
|
Returns: project summary, architecture, entry points, key dependencies.
|
|
1183
1183
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1184
1184
|
"""
|
|
@@ -1202,7 +1202,7 @@ def explain_context(repo_path: str = ".") -> dict:
|
|
|
1202
1202
|
def refactor_context(repo_path: str = ".") -> dict:
|
|
1203
1203
|
"""Structural issues and refactor opportunities for a repository.
|
|
1204
1204
|
|
|
1205
|
-
Maps to:
|
|
1205
|
+
Maps to: ask prepare-context refactor <repo_path>
|
|
1206
1206
|
Returns: structural issues, coupling hotspots, improvement opportunities.
|
|
1207
1207
|
repo_path: absolute path to the repository (default: current working directory).
|
|
1208
1208
|
"""
|
|
@@ -1226,7 +1226,7 @@ def refactor_context(repo_path: str = ".") -> dict:
|
|
|
1226
1226
|
def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> dict:
|
|
1227
1227
|
"""Untested source files and test gap analysis for a repository.
|
|
1228
1228
|
|
|
1229
|
-
Maps to:
|
|
1229
|
+
Maps to: ask prepare-context generate-tests <repo_path> [--all]
|
|
1230
1230
|
Returns: test_gaps list of untested files ranked by risk.
|
|
1231
1231
|
On large repos (>2000 classes) analysis is bounded by SOURCECODE_TESTS_TIMEOUT_MS
|
|
1232
1232
|
(default: 15000 ms). If timeout elapses, returns truncated=true with partial results.
|
|
@@ -1279,7 +1279,7 @@ def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> d
|
|
|
1279
1279
|
def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -> dict:
|
|
1280
1280
|
"""Blast-radius analysis: who calls a class and what breaks if it changes? Java only.
|
|
1281
1281
|
|
|
1282
|
-
Maps to:
|
|
1282
|
+
Maps to: ask impact <target> <repo_path> [--depth <depth>]
|
|
1283
1283
|
Returns: direct_callers, indirect_callers, endpoints_affected,
|
|
1284
1284
|
transactional_boundaries_touched, risk_score, risk_level, stats.
|
|
1285
1285
|
|
|
@@ -1334,7 +1334,7 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
1334
1334
|
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
|
-
Maps to:
|
|
1337
|
+
Maps to: ask modernize <repo_path>
|
|
1338
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
|
|
|
@@ -1368,9 +1368,9 @@ _TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
|
1368
1368
|
|
|
1369
1369
|
@mcp.tool()
|
|
1370
1370
|
def version() -> dict:
|
|
1371
|
-
"""Return
|
|
1371
|
+
"""Return ASK Engine version and MCP compatibility metadata.
|
|
1372
1372
|
|
|
1373
|
-
Maps to:
|
|
1373
|
+
Maps to: ask version
|
|
1374
1374
|
Returns structured JSON: cli_version, mcp_schema_version, compatibility_schema_version.
|
|
1375
1375
|
cli_version and mcp_schema_version are always identical (released together).
|
|
1376
1376
|
"""
|
|
@@ -1381,7 +1381,7 @@ def version() -> dict:
|
|
|
1381
1381
|
def config() -> dict:
|
|
1382
1382
|
"""Show sourcecode CLI configuration.
|
|
1383
1383
|
|
|
1384
|
-
Maps to:
|
|
1384
|
+
Maps to: ask config
|
|
1385
1385
|
"""
|
|
1386
1386
|
return _execute(["config"])
|
|
1387
1387
|
|
|
@@ -1390,7 +1390,7 @@ def config() -> dict:
|
|
|
1390
1390
|
def telemetry(action: str) -> dict:
|
|
1391
1391
|
"""Manage telemetry settings.
|
|
1392
1392
|
|
|
1393
|
-
Maps to:
|
|
1393
|
+
Maps to: ask telemetry <action>
|
|
1394
1394
|
action: one of "status" (show current state), "enable" (opt in), "disable" (opt out).
|
|
1395
1395
|
Valid values: "status" | "enable" | "disable"
|
|
1396
1396
|
"""
|
sourcecode/mcp_nudge.py
CHANGED
|
@@ -26,7 +26,7 @@ _FLAG: Path = Path.home() / ".sourcecode" / "nudge_shown"
|
|
|
26
26
|
|
|
27
27
|
_MSG = (
|
|
28
28
|
"→ Claude Desktop detected. "
|
|
29
|
-
"Run `
|
|
29
|
+
"Run `ask mcp init` to enable agent integration.\n"
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
# Module-level imports so names are patchable in tests.
|
sourcecode/path_filters.py
CHANGED
|
@@ -16,7 +16,8 @@ _TEST_SEGMENTS = frozenset({
|
|
|
16
16
|
# code lives under src/main (e.g. a Maven module that ships a test framework).
|
|
17
17
|
# A finding under one of these modules is test infrastructure, not the product.
|
|
18
18
|
_TEST_MODULE_SEGMENTS = frozenset({
|
|
19
|
-
"testsuite", "test-
|
|
19
|
+
"testsuite", "test-suite", "test-suites",
|
|
20
|
+
"test-framework", "testframework",
|
|
20
21
|
"integration-arquillian", "arquillian",
|
|
21
22
|
"test-utils", "test-util", "testutils",
|
|
22
23
|
"test-support", "testsupport",
|
sourcecode/repository_ir.py
CHANGED
|
@@ -22,6 +22,8 @@ from dataclasses import dataclass, field
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any, Iterable, Optional
|
|
24
24
|
|
|
25
|
+
from sourcecode.caller_metrics import CALLER_METRIC_RECONCILIATION
|
|
26
|
+
from sourcecode.endpoint_metrics import ENDPOINT_SURFACE_RECONCILIATION
|
|
25
27
|
from sourcecode.fqn_utils import normalize_owner_fqn as _normalize_owner_fqn
|
|
26
28
|
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
27
29
|
from sourcecode.security_config import (
|
|
@@ -3965,6 +3967,11 @@ def _assemble(
|
|
|
3965
3967
|
return {
|
|
3966
3968
|
**_base,
|
|
3967
3969
|
"route_surface": _route_surface,
|
|
3970
|
+
# SC-9 (Broadleaf field test): route_surface is the RAW surface (includes
|
|
3971
|
+
# framework dynamic-admin FQN-shaped routes the `endpoints` command filters).
|
|
3972
|
+
# Self-describe it so its count is never read as contradicting endpoints /
|
|
3973
|
+
# spring-audit totals when cross-referenced.
|
|
3974
|
+
"route_surface_note": ENDPOINT_SURFACE_RECONCILIATION,
|
|
3968
3975
|
"spring_events": _spring_events,
|
|
3969
3976
|
"analysis_gaps": _analysis_gaps,
|
|
3970
3977
|
"security_model": _security_model_asm,
|
|
@@ -4236,6 +4243,10 @@ def _build_route_surface(
|
|
|
4236
4243
|
"return_type": (sym.return_type.strip() if sym.return_type else "void"),
|
|
4237
4244
|
"stable_id": sym.stable_id,
|
|
4238
4245
|
"inheritance_depth": 0,
|
|
4246
|
+
# Provenance: which annotation family this modeled route came from.
|
|
4247
|
+
# Used downstream so a MODELED framework (JAX-RS) is never also reported
|
|
4248
|
+
# as an un-modeled "non_spring_rest_surface" (A2 field-validation fix).
|
|
4249
|
+
"framework": "jax_rs" if ann_name in _JAXRS_HTTP_ANNOTATIONS else "spring_mvc",
|
|
4239
4250
|
}
|
|
4240
4251
|
_route_entry["security_annotations"] = _sec
|
|
4241
4252
|
routes.append(_route_entry)
|
|
@@ -4721,11 +4732,16 @@ def apply_ir_size_limits(
|
|
|
4721
4732
|
if isinstance(raw_rs, list):
|
|
4722
4733
|
_rs_total = len(raw_rs)
|
|
4723
4734
|
out["route_surface"] = raw_rs[:50]
|
|
4735
|
+
# SC-9: always carry the raw-surface reconciliation note; prepend the
|
|
4736
|
+
# truncation notice when the list was capped.
|
|
4724
4737
|
if _rs_total > 50:
|
|
4725
4738
|
out["route_surface_note"] = (
|
|
4726
|
-
f"Showing 50/{_rs_total}
|
|
4727
|
-
"Remove --summary-only for full route surface."
|
|
4739
|
+
f"Showing 50/{_rs_total} route-surface entries. "
|
|
4740
|
+
"Remove --summary-only for the full route surface. "
|
|
4741
|
+
+ ENDPOINT_SURFACE_RECONCILIATION
|
|
4728
4742
|
)
|
|
4743
|
+
else:
|
|
4744
|
+
out["route_surface_note"] = ENDPOINT_SURFACE_RECONCILIATION
|
|
4729
4745
|
elif isinstance(raw_rs, dict):
|
|
4730
4746
|
# Legacy dict format with "endpoints" sub-key
|
|
4731
4747
|
raw_eps: list = raw_rs.get("endpoints") or []
|
|
@@ -5116,16 +5132,19 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
5116
5132
|
# total false negative that also disables the `validation` command downstream.
|
|
5117
5133
|
# Detection is by SYNTACTIC SHAPE (no framework knowledge): an HTTP-verb method
|
|
5118
5134
|
# name, a first argument that is a string literal looking like a path (starts
|
|
5119
|
-
# with "/", may contain :param / {param}), AND a second argument
|
|
5120
|
-
#
|
|
5121
|
-
#
|
|
5122
|
-
#
|
|
5123
|
-
#
|
|
5124
|
-
#
|
|
5125
|
-
#
|
|
5135
|
+
# with "/", may contain :param / {param}), AND a second argument that is a
|
|
5136
|
+
# HANDLER, not a literal. Matches both bare `get(...)` (static-import / Spark
|
|
5137
|
+
# style) and `app.get(...)` (Javalin style); the lookbehind only rejects an
|
|
5138
|
+
# identifier char so `forget(` is not mistaken for `get(`.
|
|
5139
|
+
# B1 fix (field validation, openmrs FormUtil): a route handler is always a
|
|
5140
|
+
# method-ref / lambda / call / identifier — never a bare literal. The negative
|
|
5141
|
+
# lookahead after the comma rejects a literal second argument, so a plain map
|
|
5142
|
+
# write like `swapChars.put("/", "slash")` is no longer mistaken for a `PUT /`
|
|
5143
|
+
# route. Reported at confidence "medium" — an occasional flagged false positive
|
|
5144
|
+
# beats a total silent false negative.
|
|
5126
5145
|
_DSL_ROUTE_RE = _re.compile(
|
|
5127
5146
|
r'(?<![A-Za-z0-9_])(get|post|put|delete|patch|head|options)\s*\(\s*'
|
|
5128
|
-
r'"(/[^"\s]*)"\s*,'
|
|
5147
|
+
r'"(/[^"\s]*)"\s*,(?!\s*(?:["0-9]|true\b|false\b|null\b))'
|
|
5129
5148
|
)
|
|
5130
5149
|
_dsl_routes: list[dict] = []
|
|
5131
5150
|
|
|
@@ -5441,6 +5460,15 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
5441
5460
|
if _webscript_descriptors:
|
|
5442
5461
|
_nonspring["webscripts"] += _webscript_descriptors
|
|
5443
5462
|
|
|
5463
|
+
# A2 fix (eureka/keycloak field validation): JAX-RS is MODELED by _build_route_surface
|
|
5464
|
+
# (routes carry framework="jax_rs"). When we actually modeled JAX-RS endpoints, it is
|
|
5465
|
+
# NOT an un-modeled "non_spring_rest_surface" — reporting it as such contradicted the
|
|
5466
|
+
# endpoints just emitted (e.g. keycloak's 689 JAX-RS routes labelled "not modeled").
|
|
5467
|
+
# Only genuinely un-modeled surfaces (WebScripts, Servlets, or JAX-RS that produced no
|
|
5468
|
+
# modeled route — client proxies / extraction gaps) remain here.
|
|
5469
|
+
if any(r.get("framework") == "jax_rs" for r in routes):
|
|
5470
|
+
_nonspring.pop("jax_rs", None)
|
|
5471
|
+
|
|
5444
5472
|
_nonspring_total = sum(_nonspring.values())
|
|
5445
5473
|
if _nonspring_total:
|
|
5446
5474
|
_frameworks = [k for k, v in _nonspring.items() if v]
|
|
@@ -5471,9 +5499,9 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
|
|
|
5471
5499
|
)
|
|
5472
5500
|
else:
|
|
5473
5501
|
_msg = (
|
|
5474
|
-
f"Additional REST surface
|
|
5475
|
-
f"
|
|
5476
|
-
f"
|
|
5502
|
+
f"Additional REST surface detected but NOT modeled here: {_fw_label}. "
|
|
5503
|
+
f"The endpoints above cover the annotation-modeled surface (Spring MVC and "
|
|
5504
|
+
f"JAX-RS); the {_fw_label} surface is additional and not included in the count."
|
|
5477
5505
|
)
|
|
5478
5506
|
result.setdefault("warnings", []).append(_msg)
|
|
5479
5507
|
|
|
@@ -5648,7 +5676,7 @@ def compute_blast_radius(
|
|
|
5648
5676
|
"message": (
|
|
5649
5677
|
f"No symbol matching {target!r} found in IR. "
|
|
5650
5678
|
"Verify the class name or FQN. "
|
|
5651
|
-
"Run `
|
|
5679
|
+
"Run `ask repo-ir <repo> --output ir.json` to inspect available symbols."
|
|
5652
5680
|
),
|
|
5653
5681
|
"direct_callers": [],
|
|
5654
5682
|
"indirect_callers": [],
|
|
@@ -6153,10 +6181,18 @@ def compute_blast_radius(
|
|
|
6153
6181
|
"no indirect callers reachable from sampled seeds (terminal sink or sparse graph). "
|
|
6154
6182
|
"Use a lower-fan-in entry point for full transitive traversal."
|
|
6155
6183
|
)
|
|
6184
|
+
# SC-6 (Broadleaf field test): the direct_callers array is a truncated display
|
|
6185
|
+
# sample. Disclose the cap and point to stats.direct_caller_count as the true
|
|
6186
|
+
# total (== explain.incoming_callers), then append the cross-command
|
|
6187
|
+
# reconciliation so len(direct_callers) is never misread as a contradiction.
|
|
6156
6188
|
if len(direct_callers) > 30:
|
|
6157
6189
|
out["direct_callers_note"] = (
|
|
6158
|
-
f"Showing 30/{n_direct} direct callers
|
|
6190
|
+
f"Showing 30/{n_direct} distinct direct callers; "
|
|
6191
|
+
f"stats.direct_caller_count = {n_direct} is the true total "
|
|
6192
|
+
f"(use --output for the full list). " + CALLER_METRIC_RECONCILIATION
|
|
6159
6193
|
)
|
|
6194
|
+
else:
|
|
6195
|
+
out["direct_callers_note"] = CALLER_METRIC_RECONCILIATION
|
|
6160
6196
|
if len(indirect_callers) > 50:
|
|
6161
6197
|
out["indirect_callers_note"] = (
|
|
6162
6198
|
f"Showing 50/{n_indirect} indirect callers. Use --output to inspect full IR."
|
sourcecode/ris.py
CHANGED
|
@@ -506,7 +506,7 @@ def get_cold_start_context(repo_root: Path) -> dict:
|
|
|
506
506
|
if not endpoints and _is_java:
|
|
507
507
|
result["endpoints_hint"] = (
|
|
508
508
|
"Java repo detected but no endpoint index found. "
|
|
509
|
-
"Call get_endpoints (or:
|
|
509
|
+
"Call get_endpoints (or: ask endpoints <path>) to populate."
|
|
510
510
|
)
|
|
511
511
|
return result
|
|
512
512
|
except Exception:
|