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/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: sourcecode activate <key>")
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="sourcecode activate <license_key>",
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: sourcecode <repo_path> --compact [--git-context]
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: sourcecode <repo_path> --agent [--git-context]
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: sourcecode endpoints <repo_path>
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: sourcecode spring-audit <repo_path> --scope <scope>
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: sourcecode migrate-check <repo_path> --min-severity <min_severity>
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: sourcecode impact-chain <symbol> <repo_path> [--depth <depth>]
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: sourcecode <repo_path>/<module> --compact
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: sourcecode prepare-context delta <repo_path> --since <since>
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: sourcecode repo-ir <repo_path> --summary-only
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: sourcecode repo-ir <path> --output ir.json
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: sourcecode prepare-context fix-bug <repo_path> [--symptom <symptom>]
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: sourcecode prepare-context review-pr <repo_path> [--since <since>]
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: sourcecode prepare-context onboard <repo_path>
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: sourcecode prepare-context explain <repo_path>
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: sourcecode prepare-context refactor <repo_path>
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: sourcecode prepare-context generate-tests <repo_path> [--all]
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: sourcecode impact <target> <repo_path> [--depth <depth>]
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: sourcecode modernize <repo_path>
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 sourcecode version and MCP compatibility metadata.
1371
+ """Return ASK Engine version and MCP compatibility metadata.
1372
1372
 
1373
- Maps to: sourcecode version
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: sourcecode config
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: sourcecode telemetry <action>
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 `sourcecode mcp init` to enable agent integration.\n"
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.
@@ -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-framework", "testframework",
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",
@@ -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} endpoints. "
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 (the trailing
5120
- # comma) the comma is what distinguishes a route registration from a 1-arg
5121
- # getter such as Map.get("/k"). Matches both bare `get(...)` (static-import /
5122
- # Spark style) and `app.get(...)` (Javalin style); the lookbehind only rejects
5123
- # an identifier char so `forget(` is not mistaken for `get(`. Reported at
5124
- # confidence "medium" an occasional flagged false positive beats a total
5125
- # silent false negative.
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 present outside Spring MVC: detected {_fw_label}. "
5475
- f"These endpoints are NOT modeled here; the count covers Spring-MVC-annotated "
5476
- f"endpoints only."
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 `sourcecode repo-ir <repo> --output ir.json` to inspect available symbols."
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. Use --output to inspect full IR."
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: sourcecode endpoints <path>) to populate."
509
+ "Call get_endpoints (or: ask endpoints <path>) to populate."
510
510
  )
511
511
  return result
512
512
  except Exception: