sourcecode 2.1.0__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 CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.1.0"
7
+ __version__ = "2.2.0"
@@ -0,0 +1,42 @@
1
+ """Single source of truth for the caller / fan-in metric reconciliation note.
2
+
3
+ Three commands report a "how many things reference this class" number, each
4
+ derived from a different graph projection. Read side by side they can look
5
+ contradictory (e.g. 215 vs 103 for the same class) — they are not. They count
6
+ different populations:
7
+
8
+ modernize.in_degree >= explain.incoming_callers ~= impact.stats.direct_caller_count
9
+
10
+ - ``modernize.in_degree`` counts graph *edges* (all edge types, symbol level, not
11
+ deduplicated per class) — a blast-radius ranking weight, always the largest, and
12
+ not a count of classes.
13
+ - ``explain.incoming_callers`` and ``impact.stats.direct_caller_count`` both count
14
+ *distinct dependent classes*. They normally match and are always the same order
15
+ of magnitude, but can differ by a small margin because each includes a slightly
16
+ different edge set (import-only references, DI-interface resolution). The
17
+ truncated ``impact.direct_callers`` array is a display sample, not the count.
18
+
19
+ Empirically verified across repositories (Broadleaf ExtensionManager: 215 / 103 /
20
+ 103; petclinic entities & repositories: equal; occasional margin-of-one divergence
21
+ on classes with import-only references). The strict-equality claim was deliberately
22
+ NOT made — the note asserts only the robust ordering and the "same distinct-class
23
+ metric" relationship.
24
+
25
+ Every command that emits one of these numbers references this same string so the
26
+ explanation can never drift or contradict itself between commands.
27
+ """
28
+
29
+ CALLER_METRIC_RECONCILIATION: str = (
30
+ "Fan-in / caller counts differ across commands BY DESIGN and are not "
31
+ "contradictory. For the same class the expected relationship is: "
32
+ "modernize.in_degree >= explain.incoming_callers ~= impact.stats.direct_caller_count. "
33
+ "modernize.in_degree = raw count of ALL incoming graph edges (imports, injects, "
34
+ "extends/implements, references, annotations), counted at symbol level and NOT "
35
+ "deduplicated per class — a blast-radius ranking weight, not a count of classes; "
36
+ "it is always the largest of the three. explain.incoming_callers and "
37
+ "impact.stats.direct_caller_count both count DISTINCT dependent classes "
38
+ "(deduplicated to class level); they normally match but can differ by a small "
39
+ "margin because each includes a slightly different edge set (e.g. import-only "
40
+ "references or DI-interface resolution). The top-level impact.direct_callers array "
41
+ "may be truncated for output size — use stats.direct_caller_count for the true total."
42
+ )
sourcecode/cli.py CHANGED
@@ -15,6 +15,7 @@ from sourcecode import __version__
15
15
  from sourcecode.error_schema import INVALID_INPUT_CODE, build_error_envelope
16
16
  from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
17
17
  from sourcecode.progress import Progress
18
+ from sourcecode.caller_metrics import CALLER_METRIC_RECONCILIATION
18
19
  from sourcecode.repository_ir import extract_java_endpoints as _extract_java_endpoints
19
20
 
20
21
 
@@ -700,7 +701,8 @@ _WELCOME_CMDS = (
700
701
 
701
702
  def _print_welcome_plain(tier: str) -> None:
702
703
  """Plain-text welcome — fallback when rich is unavailable."""
703
- lines = ["", f" ASK Engine {__version__} · {tier} · CLI: ask", "",
704
+ lines = ["", f" ASK Engine {__version__} · {tier} · CLI: ask",
705
+ " Actionable Software Knowledge Engine", "",
704
706
  " AI coding-agent context, instant.", "", " Get started:"]
705
707
  for cmd, desc in _WELCOME_CMDS:
706
708
  lines.append(f" {cmd.ljust(34)}{desc}")
@@ -747,8 +749,9 @@ def _print_welcome() -> None:
747
749
  t.append(" ")
748
750
  t.append(tier, style=tier_style)
749
751
  t.append("\n")
750
- # "ENGINE" label beneath the wordmark.
751
- t.append(_WELCOME_ENGINE + "\n", style="bold dim cyan")
752
+ # "ENGINE" label beneath the wordmark, with the acronym expansion.
753
+ t.append(_WELCOME_ENGINE, style="bold dim cyan")
754
+ t.append(" Actionable Software Knowledge\n", style="dim")
752
755
 
753
756
  t.append("\nAI coding-agent context, instant.\n\n", style="white")
754
757
 
@@ -3911,7 +3914,12 @@ def endpoints_cmd(
3911
3914
  )
3912
3915
  raise typer.Exit(code=1)
3913
3916
 
3914
- data = _extract_java_endpoints(target)
3917
+ _prog = Progress()
3918
+ _prog.start("scanning endpoints")
3919
+ try:
3920
+ data = _extract_java_endpoints(target)
3921
+ finally:
3922
+ _prog.finish()
3915
3923
 
3916
3924
  # Update RIS api_surface section (non-fatal side-effect).
3917
3925
  try:
@@ -4358,6 +4366,9 @@ def export_cmd(
4358
4366
  )
4359
4367
  raise typer.Exit(1)
4360
4368
 
4369
+ _prog = Progress()
4370
+ _prog.start("scanning repository")
4371
+
4361
4372
  file_list = [
4362
4373
  f for f in find_java_files(root)
4363
4374
  if "/test/" not in f and "/tests/" not in f
@@ -4384,6 +4395,7 @@ def export_cmd(
4384
4395
  _integrations,
4385
4396
  endpoint_meta=_ep_data,
4386
4397
  )
4398
+ _prog.finish()
4387
4399
  output = _serialize_dict(data, format)
4388
4400
  _emit_command_output(output, output_path, copy,
4389
4401
  success_msg=f"C4 architecture export written to {output_path}")
@@ -4413,6 +4425,7 @@ def export_cmd(
4413
4425
  ContextGraph.build(file_list, root)
4414
4426
  ).as_report(len(file_list))
4415
4427
 
4428
+ _prog.finish()
4416
4429
  output = _serialize_dict(data, format)
4417
4430
  _emit_command_output(output, output_path, copy,
4418
4431
  success_msg=f"Export written to {output_path}")
@@ -4495,6 +4508,8 @@ def validation_cmd(
4495
4508
 
4496
4509
  from sourcecode.context_graph import ContextGraph
4497
4510
  from sourcecode.validation_surface import build_validation_surface
4511
+ _prog = Progress()
4512
+ _prog.start("mapping validation surface")
4498
4513
  # Structural facts come from the ContextGraph — the single access layer.
4499
4514
  _graph = ContextGraph.build_from_root(target)
4500
4515
  data = build_validation_surface(target, graph=_graph)
@@ -4527,6 +4542,7 @@ def validation_cmd(
4527
4542
  if _note:
4528
4543
  data["note"] = _note
4529
4544
 
4545
+ _prog.finish()
4530
4546
  output = _serialize_dict(data, format)
4531
4547
  _summary = data.get("summary", {})
4532
4548
  # Human-facing heads-up when the result is empty purely because no OpenAPI
@@ -4750,6 +4766,8 @@ def spring_audit_cmd(
4750
4766
  success_msg=f"Spring audit written to {output_path}")
4751
4767
  return
4752
4768
 
4769
+ _prog = Progress()
4770
+ _prog.start(f"auditing {len(file_list)} Java files")
4753
4771
  cir = ContextGraph.build(file_list, target).cir
4754
4772
  _model = SpringSemanticModel.build(cir)
4755
4773
 
@@ -4802,6 +4820,7 @@ def spring_audit_cmd(
4802
4820
  except Exception:
4803
4821
  pass
4804
4822
 
4823
+ _prog.finish()
4805
4824
  if format == "github-comment":
4806
4825
  output = _render_spring_audit_github_comment(combined, min_severity)
4807
4826
  else:
@@ -4920,7 +4939,12 @@ def migrate_check_cmd(
4920
4939
 
4921
4940
  _file_limitations: list[str] = []
4922
4941
  file_list = find_java_files(target, limitations=_file_limitations)
4923
- report = run_migrate_check(file_list, target, min_severity=min_severity)
4942
+ _prog = Progress()
4943
+ _prog.start(f"checking migration ({len(file_list)} files)")
4944
+ try:
4945
+ report = run_migrate_check(file_list, target, min_severity=min_severity)
4946
+ finally:
4947
+ _prog.finish()
4924
4948
  if _file_limitations:
4925
4949
  report.limitations.extend(_file_limitations)
4926
4950
 
@@ -5079,6 +5103,8 @@ def impact_chain_cmd(
5079
5103
  success_msg=f"Impact chain written to {output_path}")
5080
5104
  return
5081
5105
 
5106
+ _prog = Progress()
5107
+ _prog.start(f"analyzing impact ({len(file_list)} files)")
5082
5108
  cir = ContextGraph.build(file_list, target).cir
5083
5109
  _model = SpringSemanticModel.build(cir)
5084
5110
 
@@ -5086,6 +5112,7 @@ def impact_chain_cmd(
5086
5112
  from sourcecode.spring_event_topology import run_event_topology
5087
5113
  evt_result = run_event_topology(cir, symbol, model=_model)
5088
5114
  data = evt_result.to_dict()
5115
+ _prog.finish()
5089
5116
  output = _serialize_dict(data, format)
5090
5117
  _emit_command_output(
5091
5118
  output, output_path, copy,
@@ -5101,6 +5128,7 @@ def impact_chain_cmd(
5101
5128
  result = run_impact_chain(cir, symbol, depth=depth, root=target, model=_model)
5102
5129
 
5103
5130
  data = result.to_dict()
5131
+ _prog.finish()
5104
5132
  output = _serialize_dict(data, format)
5105
5133
  _emit_command_output(
5106
5134
  output, output_path, copy,
@@ -5229,9 +5257,14 @@ def pr_impact_cmd(
5229
5257
  success_msg=f"PR impact report written to {output_path}")
5230
5258
  return
5231
5259
 
5232
- cir = ContextGraph.build(file_list, target).cir
5233
- model = SpringSemanticModel.build(cir)
5234
- report = run_pr_impact(cir, changed_files, root=target, model=model)
5260
+ _prog = Progress()
5261
+ _prog.start(f"analyzing PR impact ({len(file_list)} files)")
5262
+ try:
5263
+ cir = ContextGraph.build(file_list, target).cir
5264
+ model = SpringSemanticModel.build(cir)
5265
+ report = run_pr_impact(cir, changed_files, root=target, model=model)
5266
+ finally:
5267
+ _prog.finish()
5235
5268
 
5236
5269
  output = _serialize_dict(report.to_dict(), "json") if format == "json" else report.render_text()
5237
5270
  _emit_command_output(
@@ -5348,9 +5381,14 @@ def explain_cmd(
5348
5381
  )
5349
5382
  raise typer.Exit(code=1)
5350
5383
 
5351
- cir = ContextGraph.build(file_list, target).cir
5352
- model = SpringSemanticModel.build(cir)
5353
- explanation = explain_class(class_name, cir, model)
5384
+ _prog = Progress()
5385
+ _prog.start(f"explaining {class_name} ({len(file_list)} files)")
5386
+ try:
5387
+ cir = ContextGraph.build(file_list, target).cir
5388
+ model = SpringSemanticModel.build(cir)
5389
+ explanation = explain_class(class_name, cir, model)
5390
+ finally:
5391
+ _prog.finish()
5354
5392
 
5355
5393
  if format == "json":
5356
5394
  output = _json.dumps(explanation.to_dict(), indent=2, ensure_ascii=False)
@@ -5663,7 +5701,56 @@ def _partition_static_unreferenced(nodes: list[dict], root: Path) -> tuple[list[
5663
5701
  if files_scanned >= _CONFIG_SCAN_MAX_FILES or not unresolved_simple:
5664
5702
  break
5665
5703
 
5666
- unreferenced = [n for n in nodes if n["fqn"] not in dispatched_fqns]
5704
+ # 3. Nested-type qualified-reference scan (SC-2 residual). The static call-graph
5705
+ # does not emit an edge for a nested-type member access in ordinary method-body
5706
+ # code — e.g. `PropertyType.AdminGroupPresentation.NAME` credits the enclosing
5707
+ # type, not the nested holder — so a referenced nested class can look
5708
+ # zero-degree. When a candidate is a nested type (its second-to-last FQN segment
5709
+ # is an UpperCamel enclosing type) and its qualified `Outer.Nested` form appears
5710
+ # in some OTHER Java source, it is statically referenced: neither dead nor
5711
+ # framework-dispatched. Structural, name-agnostic (pattern derived from the
5712
+ # candidate's own FQN).
5713
+ import re as _re_nested
5714
+ statically_referenced: set[str] = set()
5715
+ nested_patterns: dict[str, "tuple"] = {}
5716
+ for n in nodes:
5717
+ if n["fqn"] in dispatched_fqns:
5718
+ continue
5719
+ parts = (n.get("fqn") or "").split(".")
5720
+ if len(parts) >= 2 and parts[-2][:1].isupper():
5721
+ outer, nested = parts[-2], parts[-1]
5722
+ nested_patterns[n["fqn"]] = (
5723
+ _re_nested.compile(r"\b" + _re_nested.escape(outer) + r"\." + _re_nested.escape(nested) + r"\b"),
5724
+ n.get("source_file") or "",
5725
+ )
5726
+ if nested_patterns:
5727
+ files_scanned = 0
5728
+ for dirpath, dirnames, filenames in os.walk(root):
5729
+ dirnames[:] = [d for d in dirnames
5730
+ if d not in {".git", "build", "out", "target", "node_modules", ".gradle"}]
5731
+ for fname in filenames:
5732
+ if not fname.endswith(".java"):
5733
+ continue
5734
+ if files_scanned >= _CONFIG_SCAN_MAX_FILES or len(statically_referenced) == len(nested_patterns):
5735
+ break
5736
+ fpath = os.path.join(dirpath, fname)
5737
+ rel = os.path.relpath(fpath, root)
5738
+ try:
5739
+ with open(fpath, "r", encoding="utf-8", errors="replace") as fh:
5740
+ text = fh.read(_CONFIG_SCAN_MAX_BYTES)
5741
+ except OSError:
5742
+ continue
5743
+ files_scanned += 1
5744
+ for fqn, (pat, own) in nested_patterns.items():
5745
+ if fqn in statically_referenced or rel == own:
5746
+ continue
5747
+ if pat.search(text):
5748
+ statically_referenced.add(fqn)
5749
+ if files_scanned >= _CONFIG_SCAN_MAX_FILES or len(statically_referenced) == len(nested_patterns):
5750
+ break
5751
+
5752
+ unreferenced = [n for n in nodes
5753
+ if n["fqn"] not in dispatched_fqns and n["fqn"] not in statically_referenced]
5667
5754
  dispatched = [n for n in nodes if n["fqn"] in dispatched_fqns]
5668
5755
  return unreferenced, dispatched
5669
5756
 
@@ -5870,8 +5957,13 @@ def modernize_cmd(
5870
5957
  "edge_count": _cnt,
5871
5958
  "reverse_edge_count": _reverse,
5872
5959
  # A mutual (cyclic) dependency is the actual "tangle" — both modules
5873
- # reference each other, so neither can be extracted independently.
5960
+ # reference each other, so neither can be extracted independently. A
5961
+ # one-way (directional) dependency is normal layering, NOT a tangle:
5962
+ # TANGLE-1 (petclinic field test) surfaced owner→model / vet→model
5963
+ # (reverse_edge_count:0) being labelled "tangles" when they are plain
5964
+ # base-model usage. coupling_type states which it is, honestly.
5874
5965
  "mutual": _reverse > 0,
5966
+ "coupling_type": "cyclic" if _reverse > 0 else "directional",
5875
5967
  "example": _pair_example.get((_a, _b), ""),
5876
5968
  })
5877
5969
  # Surface mutual tangles first (highest decomposition risk), then by strength.
@@ -5947,17 +6039,10 @@ def modernize_cmd(
5947
6039
  {"fqn": n["fqn"], "in_degree": n.get("in_degree", 0), "role": n.get("role", "other")}
5948
6040
  for n in coupling_nodes
5949
6041
  ],
5950
- # BUG #6 (Broadleaf field test): make the in_degree metric self-describing
5951
- # so it is not confused with `explain`'s caller count. They differ by design.
5952
- "high_coupling_nodes_note": (
5953
- "in_degree = raw count of incoming graph edges across ALL edge types "
5954
- "(imports, injects, extends/implements, references, annotations), "
5955
- "counted at symbol level. This is deliberately larger than and NOT "
5956
- "equal to `ask explain`'s caller count, which reports DISTINCT "
5957
- "dependent classes (DI dependents + reverse-call-graph callers, "
5958
- "deduplicated to class level). Use in_degree for blast-radius ranking, "
5959
- "explain's caller list for the concrete dependents to inspect."
5960
- ),
6042
+ # SC-6 (Broadleaf field test): make the in_degree metric self-describing
6043
+ # so it is not confused with `explain`'s / `impact`'s caller counts. All
6044
+ # three share one reconciliation note so they can never contradict.
6045
+ "high_coupling_nodes_note": CALLER_METRIC_RECONCILIATION,
5961
6046
  "statically_unreferenced": [
5962
6047
  {"fqn": n["fqn"], "type": n.get("type", ""), "role": n.get("role", "other")}
5963
6048
  for n in dead_zones
@@ -5978,10 +6063,12 @@ def modernize_cmd(
5978
6063
  "cross_module_tangles_note": (
5979
6064
  "Directed inter-subsystem coupling measured from structural graph "
5980
6065
  "edges (imports/injects/extends/implements/references/calls). "
5981
- "edge_count = edges from_package→to_package; mutual=true marks a "
5982
- "bidirectional (cyclic) dependency — the actual tangle that blocks "
5983
- "independent module extraction. Empty means no cross-subsystem "
5984
- "structural coupling was detected."
6066
+ "edge_count = edges from_package→to_package. coupling_type=cyclic "
6067
+ "(mutual=true) is a bidirectional dependency — the actual tangle "
6068
+ "that blocks independent module extraction; coupling_type="
6069
+ "directional (reverse_edge_count=0) is a normal one-way layered "
6070
+ "dependency, listed for coupling strength, NOT a tangle. Empty "
6071
+ "means no cross-subsystem structural coupling was detected."
5985
6072
  ),
5986
6073
  # BUG-05 fix: don't recommend "Start with hotspot_candidates" when the list is empty.
5987
6074
  "recommendation": (
@@ -5992,7 +6079,9 @@ def modernize_cmd(
5992
6079
  )
5993
6080
  + "statically_unreferenced lists classes with no Java callers — review "
5994
6081
  + "for framework dispatch (XML/reflection/SPI) before removing. "
5995
- + "Cross-module tangles indicate coupling worth decomposing."
6082
+ + "In cross_module_tangles, coupling_type=cyclic entries are the "
6083
+ + "real tangles to decompose first; directional entries are normal "
6084
+ + "layering ranked by coupling strength."
5996
6085
  ),
5997
6086
  }
5998
6087
 
@@ -176,6 +176,28 @@ class JavaDetector(AbstractDetector):
176
176
  frameworks = [f for f in frameworks if f.name != "MyBatis"]
177
177
 
178
178
  entry_points = self._collect_entry_points(context)
179
+
180
+ # C1 (Eureka field test): a repo whose REST surface is JAX-RS / Jakarta
181
+ # REST — declared on the source via @Path/@GET, not through a web framework
182
+ # coordinate in the build manifest — must still surface that in the headline
183
+ # stack. Eureka wires JAX-RS through Jersey 1.x (com.sun.jersey) + jsr311-api
184
+ # + Guice, none of which the manifest-coordinate scan models, and its child
185
+ # build.gradle files are not scanned at all — yet it ships 18 @Path resource
186
+ # classes. Without this the headline read "Java library / package" while the
187
+ # endpoints command listed a full JAX-RS API. When the annotation scan found
188
+ # JAX-RS entry points and no Jakarta-EE framework was inferred from the
189
+ # manifest, surface it so the stack (and project_type) reflect the REST API
190
+ # the endpoints already prove. Structural (VAI): reasons from the JAX-RS
191
+ # entry points found in source, not from a vendor dependency-name allowlist.
192
+ # detected_via carries no path form, so it is treated as repo-wide evidence
193
+ # (never localized to an adapter submodule).
194
+ if not any(f.name == "Jakarta EE" for f in frameworks) and any(
195
+ ep.kind in ("jax_rs_controller", "jax_rs_provider") for ep in entry_points
196
+ ):
197
+ frameworks.append(FrameworkDetection(
198
+ name="Jakarta EE", source="annotation", detected_via=["jax_rs_endpoints"],
199
+ ))
200
+
179
201
  transactional_classes = self._collect_transactional_classes(context, all_paths)
180
202
  stack = StackDetection(
181
203
  stack="java",
@@ -459,7 +481,13 @@ class JavaDetector(AbstractDetector):
459
481
  # 1. @SpringBootApplication entry: Application.java / Main.java by name
460
482
  # Exclude test trees: test helpers like AdminApplication.java in
461
483
  # integration/src/test/java/ must not be treated as production entrypoints.
462
- from sourcecode.path_filters import is_test_path as _is_test_path
484
+ # B2 (openmrs field test): exclude not just src/test/ trees but whole
485
+ # test-harness MODULES whose sources sit under src/main — e.g. openmrs'
486
+ # `test-suite/module/omod/src/main/java/.../TestModuleController.java`, a
487
+ # scaffolding @Controller (`GET /module/testmodule/hello`) that a narrow
488
+ # src/test/ check misses, so it surfaced as a production entry point. The
489
+ # broader is_test_or_fixture_path catches test-suite / *-test / *-it modules.
490
+ from sourcecode.path_filters import is_test_or_fixture_path as _is_test_path
463
491
  # BUG #3 (Alfresco field test): a `*Application.java` / `*Main.java` NAME does
464
492
  # not make a file a bootstrap entry point. Alfresco has XSD-generated JAXB
465
493
  # `Application.java` model classes (no main(), no bootstrap annotation) in a
@@ -0,0 +1,42 @@
1
+ """Single source of truth for the endpoint / route-surface reconciliation note.
2
+
3
+ Three commands report an endpoint count, each from a different point in the
4
+ pipeline. Read side by side they look contradictory (e.g. 245 vs 225 vs 220 for
5
+ one repo) — they are not. Expected ordering for a single repo:
6
+
7
+ repo-ir.route_surface >= endpoints.total >= spring-audit.endpoints_analyzed
8
+
9
+ - ``repo-ir.route_surface`` is the RAW structural surface (every @RequestMapping /
10
+ @Path handler after inheritance projection and JAX-RS sub-resource-locator
11
+ composition). It still contains framework dynamic-admin routes whose path is a
12
+ Java FQN (e.g. ``/org.broadleafcommerce.core.search.domain.FieldImpl``), which
13
+ the ``endpoints`` command filters out — so it is NOT the canonical API surface.
14
+ - ``endpoints.total`` is the canonical REST/API surface (FQN-shaped framework
15
+ routes removed). This is the number to cite for "how many endpoints".
16
+ - ``spring-audit.endpoints_analyzed`` is that canonical surface further deduplicated
17
+ by (method, path, controller, handler) for security analysis.
18
+
19
+ The exact arithmetic between the three is intentionally NOT asserted (the pipelines
20
+ differ in dedup/expansion, so route_surface minus FQN-paths does not equal
21
+ endpoints.total). Only the robust ordering and the population of each are stated.
22
+
23
+ ``spring-audit`` already documents its own count inline; this note gives repo-ir's
24
+ raw ``route_surface`` the same self-description so no reader mistakes it for the
25
+ canonical endpoint total.
26
+ """
27
+
28
+ ENDPOINT_SURFACE_RECONCILIATION: str = (
29
+ "route_surface is the RAW structural route surface: every @RequestMapping/@Path "
30
+ "handler after inheritance projection and JAX-RS sub-resource-locator composition. "
31
+ "It is NOT the canonical API surface — it still contains framework dynamic-admin "
32
+ "routes whose path is a Java FQN (e.g. /org.broadleafcommerce...), which the "
33
+ "`endpoints` command excludes. Endpoint counts differ across commands BY DESIGN "
34
+ "and are not contradictory; expected ordering for one repo is "
35
+ "repo-ir.route_surface (raw, includes FQN-shaped framework routes) >= "
36
+ "endpoints.total (canonical REST/API surface, FQN-shaped routes filtered out) >= "
37
+ "spring-audit.metadata.endpoints_analyzed (canonical surface further deduplicated "
38
+ "by (method, path, controller, handler) for security analysis). Cite "
39
+ "endpoints.total for the real API surface; the pipelines differ in dedup and "
40
+ "prefix expansion, so the three counts are related by this ordering, not by exact "
41
+ "arithmetic."
42
+ )
sourcecode/explain.py CHANGED
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  from dataclasses import dataclass, field
18
18
  from typing import TYPE_CHECKING, Optional
19
19
 
20
+ from sourcecode.caller_metrics import CALLER_METRIC_RECONCILIATION
20
21
  from sourcecode.fqn_utils import normalize_owner_fqn
21
22
 
22
23
  if TYPE_CHECKING:
@@ -121,6 +122,7 @@ class ClassExplanation:
121
122
  "purpose": self.purpose,
122
123
  "public_methods": self.public_methods,
123
124
  "incoming_callers": self.incoming_callers,
125
+ "incoming_callers_note": CALLER_METRIC_RECONCILIATION,
124
126
  "outgoing_deps": self.outgoing_deps,
125
127
  "events_published": self.events_published,
126
128
  "events_consumed": self.events_consumed,
@@ -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
 
@@ -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."
@@ -179,7 +179,9 @@ class SemanticIntegrationEngine:
179
179
 
180
180
  def detect(self) -> list[Integration]:
181
181
  """All outbound integrations recoverable from typed facts, deterministically
182
- ordered. One record per (type, kind, client). Targets are attributed by
182
+ ordered. One record per semantic point (kind + evidence location): a type that
183
+ touches several client libraries of the same kind counts once. Targets are
184
+ attributed by
183
185
  annotation args (declarative) or by literal scheme (imperative), never by a
184
186
  receiver variable name."""
185
187
  per_type: dict[str, list[Integration]] = {}
@@ -231,7 +233,21 @@ class SemanticIntegrationEngine:
231
233
  per_type[t.fqn] = list(recs.values())
232
234
 
233
235
  out = [r for recs in per_type.values() for r in recs]
234
- return sorted(out, key=lambda i: (i.kind, i.client, i.evidence))
236
+ out.sort(key=lambda i: (i.kind, i.client, i.evidence))
237
+ # REG-1 (Broadleaf field test): one integration per semantic point =
238
+ # (kind, evidence location). A single type touching several client-library
239
+ # types of the SAME kind — e.g. spring-ldap + spring-security-ldap on one
240
+ # LdapUserDetailsMapper, or javamail + spring-mail on one MessageCreator —
241
+ # previously emitted one record per client label, double-counting the same
242
+ # integration. Collapse to a single record per (kind, evidence), preferring
243
+ # one that carries a resolved target; ties broken by the deterministic
244
+ # (kind, client, evidence) order established above.
245
+ deduped: dict[tuple, Integration] = {}
246
+ for r in out:
247
+ k = (r.kind, r.evidence)
248
+ if k not in deduped or (r.target and deduped[k].target is None):
249
+ deduped[k] = r
250
+ return list(deduped.values())
235
251
 
236
252
  def _coverage(self, n_records: int, n_files: int) -> "tuple[str, str]":
237
253
  """Honest coverage signal (mirrors integration_detector): a low count on a
sourcecode/serializer.py CHANGED
@@ -462,11 +462,31 @@ def _mybatis_pairing(sm: "SourceMap", *, full: bool = False) -> "Optional[dict[s
462
462
 
463
463
 
464
464
  def _spring_boot_version(sm: "SourceMap") -> "Optional[str]":
465
- """Extract Spring Boot version from detected frameworks."""
465
+ """Extract Spring Boot version from detected frameworks.
466
+
467
+ A3 fix (field validation, Broadleaf): when Spring Boot IS detected but the
468
+ stack detector could not attach a version (e.g. the version lives in a
469
+ `<spring.boot.version>` pom property rather than spring-boot-starter-parent),
470
+ fall back to migrate-check's property-resolving detector so `--compact`
471
+ reports the SAME version migrate-check already knows — the engine must not
472
+ surface a fact in one command and hide it in another. Gated on Boot already
473
+ being detected, so no phantom version is ever introduced.
474
+ """
475
+ _boot_detected = False
466
476
  for s in sm.stacks:
467
477
  for fw in s.frameworks:
468
- if fw.name == "Spring Boot" and fw.version:
469
- return fw.version
478
+ if fw.name == "Spring Boot":
479
+ if fw.version:
480
+ return fw.version
481
+ _boot_detected = True
482
+ if _boot_detected:
483
+ _root = getattr(sm.metadata, "analyzed_path", None) if sm.metadata else None
484
+ if _root:
485
+ try:
486
+ from sourcecode.migrate_check import _detect_spring_boot
487
+ return _detect_spring_boot(Path(_root), 0)[1]
488
+ except Exception:
489
+ return None
470
490
  return None
471
491
 
472
492
 
@@ -122,9 +122,17 @@ class TransactionBoundary:
122
122
  class TransactionBoundaryIndex:
123
123
  """Index of all @Transactional boundaries in a repository."""
124
124
 
125
- # FQN → boundary (both class-level and method-level)
125
+ # FQN → boundary (both class-level and method-level). Keyed by FQN for
126
+ # effective_boundary() call-graph lookups; OVERLOADED methods share an FQN
127
+ # (e.g. findAll() and findAll(Pageable) → Repo#findAll) so this dict collapses
128
+ # them — do NOT count from it (see all_declared / A1 field-validation fix).
126
129
  by_symbol: dict[str, TransactionBoundary] = field(default_factory=dict)
127
130
 
131
+ # Every declared @Transactional boundary, one entry per annotation site.
132
+ # Unlike by_symbol this does NOT collapse overloaded methods, so it is the
133
+ # authoritative source for counts/stats.
134
+ all_declared: list[TransactionBoundary] = field(default_factory=list)
135
+
128
136
  # class FQN → list of method-level boundaries declared on that class
129
137
  by_class: dict[str, list[TransactionBoundary]] = field(default_factory=dict)
130
138
 
@@ -152,17 +160,19 @@ class TransactionBoundaryIndex:
152
160
  return list(self.by_symbol.values())
153
161
 
154
162
  def stats(self) -> dict:
163
+ # Count from all_declared (one entry per annotation site) so overloaded
164
+ # @Transactional methods are not collapsed by the FQN-keyed by_symbol.
155
165
  n_class = len(self.class_level)
156
- n_method = sum(1 for b in self.by_symbol.values() if b.scope == "method")
166
+ n_method = sum(1 for b in self.all_declared if b.scope == "method")
157
167
  propagations: dict[str, int] = {}
158
- for b in self.by_symbol.values():
168
+ for b in self.all_declared:
159
169
  propagations[b.propagation] = propagations.get(b.propagation, 0) + 1
160
170
  return {
161
- "total": len(self.by_symbol),
171
+ "total": len(self.all_declared),
162
172
  "class_level": n_class,
163
173
  "method_level": n_method,
164
174
  "propagations": propagations,
165
- "read_only_count": sum(1 for b in self.by_symbol.values() if b.read_only),
175
+ "read_only_count": sum(1 for b in self.all_declared if b.read_only),
166
176
  }
167
177
 
168
178
 
@@ -353,6 +363,9 @@ def build_tx_index(cir: "CanonicalRepositoryIR") -> TransactionBoundaryIndex:
353
363
  boundary = _build_boundary(fqn, scope, raw_args, source_file, modifiers)
354
364
 
355
365
  index.by_symbol[fqn] = boundary
366
+ # Authoritative per-site list — overloaded methods share an FQN and would
367
+ # collapse in by_symbol; all_declared keeps one entry per annotation site.
368
+ index.all_declared.append(boundary)
356
369
 
357
370
  if scope == "class":
358
371
  index.class_level[fqn] = boundary
@@ -194,8 +194,12 @@ class _TX001ProxyBypass:
194
194
  model: Optional[SpringSemanticModel] = None,
195
195
  ) -> list[SpringFinding]:
196
196
  findings: list[SpringFinding] = []
197
-
198
- for boundary in tx_index.all_boundaries():
197
+ # A1-RESIDUAL: iterate all_declared (one entry per annotation site), not the
198
+ # FQN-keyed all_boundaries which collapses overloads — a public overload
199
+ # overwriting a private one would hide the proxy-bypass risk. Dedupe by
200
+ # finding id (symbol-keyed) so two risky overloads of the same FQN yield one.
201
+ _seen_ids: set[str] = set()
202
+ for boundary in tx_index.all_declared:
199
203
  if boundary.scope != "method":
200
204
  continue
201
205
  if not boundary.is_proxy_bypass_risk:
@@ -209,10 +213,14 @@ class _TX001ProxyBypass:
209
213
  if problematic_modifier == "private"
210
214
  else "Spring CGLIB proxy cannot override final methods"
211
215
  )
216
+ _fid = SpringFinding.make_id(self.pattern_id, boundary.symbol)
217
+ if _fid in _seen_ids:
218
+ continue
219
+ _seen_ids.add(_fid)
212
220
  simple_name = boundary.symbol.rsplit(".", 1)[-1].replace("#", ".")
213
221
 
214
222
  findings.append(SpringFinding(
215
- id=SpringFinding.make_id(self.pattern_id, boundary.symbol),
223
+ id=_fid,
216
224
  pattern_id=self.pattern_id,
217
225
  category="tx",
218
226
  severity=self.severity,
@@ -550,8 +558,11 @@ class _TX005ExceptionSwallowing:
550
558
  return []
551
559
 
552
560
  findings: list[SpringFinding] = []
553
-
554
- for boundary in tx_index.all_boundaries():
561
+ # A1-RESIDUAL: iterate all_declared so a write overload that swallows is not
562
+ # hidden by a read_only overload of the same FQN winning the by_symbol slot
563
+ # (the read_only gate below would skip the whole FQN). Dedupe by finding id.
564
+ _seen_ids: set[str] = set()
565
+ for boundary in tx_index.all_declared:
555
566
  if boundary.scope != "method":
556
567
  continue
557
568
  if not boundary.source_file:
@@ -573,9 +584,13 @@ class _TX005ExceptionSwallowing:
573
584
  if not self._has_swallowed_exception(source, boundary.symbol):
574
585
  continue
575
586
 
587
+ _fid = SpringFinding.make_id(self.pattern_id, boundary.symbol)
588
+ if _fid in _seen_ids:
589
+ continue
590
+ _seen_ids.add(_fid)
576
591
  simple_name = boundary.symbol.rsplit(".", 1)[-1].replace("#", ".")
577
592
  findings.append(SpringFinding(
578
- id=SpringFinding.make_id(self.pattern_id, boundary.symbol),
593
+ id=_fid,
579
594
  pattern_id=self.pattern_id,
580
595
  category="tx",
581
596
  severity=self.severity,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.1.0
3
+ Version: 2.2.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
@@ -38,11 +38,11 @@ Description-Content-Type: text/markdown
38
38
 
39
39
  # ASK Engine
40
40
 
41
- > **Persistent structural intelligence for AI coding agents.**
41
+ > **ASK — Actionable Software Knowledge.** Persistent structural intelligence for AI coding agents.
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-2.1.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-2.2.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -84,7 +84,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
84
84
  # pip / pipx
85
85
  pipx install sourcecode # or: pip install sourcecode
86
86
 
87
- ask version # ask 2.1.0
87
+ ask version # ask 2.2.0
88
88
  ```
89
89
 
90
90
  > **Package vs. command.** The install package is named `sourcecode` this release
@@ -1,14 +1,15 @@
1
- sourcecode/__init__.py,sha256=6IP6ug5fLqU_NKOIzfMwRs-jSKYDBb21TLg1tIzLWAk,308
1
+ sourcecode/__init__.py,sha256=QOkIfC7KA2tM2ix9_wUgS6fHiERh2v_TTNhjw8RBb1o,308
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=6_wC4TYuBYxaqckZS0I1vBSMRPeH2vzlDB48gXwOOd4,46627
4
4
  sourcecode/architecture_summary.py,sha256=UaxmUU77oTlWxttMU3c6PxHrU-Q6q5N31TeU_NWWTdU,24297
5
5
  sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
6
6
  sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
7
7
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
8
+ sourcecode/caller_metrics.py,sha256=HG8RCOkpzzsEGdnMRob6byrwjoj5__t0TEsBj4yR7ks,2574
8
9
  sourcecode/canonical_ir.py,sha256=U69m8Vkr0p407xF1q5y1KguHXWEAubYw8NFHR9hDNJk,29178
9
10
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
10
11
  sourcecode/classifier.py,sha256=YTTCoRdcLEFRVcql9Ow1dE7eYQj0jq2rgx32bRDnb1k,13852
11
- sourcecode/cli.py,sha256=PksGF5DiVcPU0SUHw0vKwQA-9w934ZMKCix1K4WX8Rs,289590
12
+ sourcecode/cli.py,sha256=CKy6zdBfecnT6reYb749pLY4NqH1_Qy7c8DHnBWamrU,293593
12
13
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
13
14
  sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
14
15
  sourcecode/context_graph.py,sha256=8MVDu06bPhvRDTgUqWEvRME2fOw34bFLf4OUILZd13I,43527
@@ -21,11 +22,12 @@ sourcecode/dependency_analyzer.py,sha256=fQCaWQ7_BNcVgZv8gweJCwJ1CGlXsz8nw_tqN3a
21
22
  sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
22
23
  sourcecode/dynamic_argument_surface.py,sha256=iRzrJhs57UXrGvLhNPiTA-e_sdO6ka-wNqc0wIiegiM,2554
23
24
  sourcecode/endpoint_literals.py,sha256=Qf4gTZzvNSFDGDuOvF0YRL9NvaYKKj24klhR5LIOaXc,3263
25
+ sourcecode/endpoint_metrics.py,sha256=wkAonXe34ItHlvlGur3YRFUrcZfg6GdiiwiE4bMa21Y,2590
24
26
  sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4-W3zWE,5322
25
27
  sourcecode/env_analyzer.py,sha256=aNTyYgQk5noJDfJU6FmasmESOHfiomyJw5EvZqjy6qc,22213
26
28
  sourcecode/error_schema.py,sha256=uwosfNaSujtYm11_732Hu92z5ITV040fQDaIyefSvR4,1683
27
29
  sourcecode/evidence_provider.py,sha256=GSSL44JEaouO5AHks2sB3d1YvC9xIKIld1yBYxZpXxo,4277
28
- sourcecode/explain.py,sha256=DyGRH7DXvtuioE1TF7kq8xwprrSSHRXErm840RJ9pIc,21055
30
+ sourcecode/explain.py,sha256=bo4sy8qkEstIF3sIefECKRYJBK97C6cMjyzRa8USCdI,21189
29
31
  sourcecode/file_chunker.py,sha256=3vkM3mDQ5eE_yTPvUgjyjpGFBIjkW6_mrBmIbrylnA8,16444
30
32
  sourcecode/file_classifier.py,sha256=A0fEABqtfVu1MfoaxnPAvGpZgneGgVXlJDhT74NYXxE,15314
31
33
  sourcecode/format_contract.py,sha256=1cTNqwP8geA2hbQoBHUPgX3_vSh3l8guJT_jmgEnFF8,3466
@@ -40,7 +42,7 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
40
42
  sourcecode/migrate_check.py,sha256=HJhvHuvXDGTJSRQU9_AMx0L94h2TImC1DCGs3m3RTu0,108199
41
43
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
42
44
  sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
43
- sourcecode/path_filters.py,sha256=VnaD9jxZVNzluackNSTCdIddwzAqIseuSCs_A-gpCDM,6898
45
+ sourcecode/path_filters.py,sha256=T1y0SJsmUJBju2Hh_-z40kURAowUEq42FpPgWLbOaY0,6931
44
46
  sourcecode/pr_comment_renderer.py,sha256=KmcjMruhR44gjzMDJwjBSkWP9QEvh8xWBLyxzxoRbj0,14542
45
47
  sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
46
48
  sourcecode/prepare_context.py,sha256=PYygAKL_jwz8gGElgM9mroUSih18UKSSjv08ndfl4aA,222877
@@ -50,7 +52,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
50
52
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
51
53
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
52
54
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
53
- sourcecode/repository_ir.py,sha256=mMmEAcs5aTS-eHv48tVUQToLylONPRoBAsM_VM3mAmQ,281526
55
+ sourcecode/repository_ir.py,sha256=3DONydNhy-UU1R_1l-caePlBiUlZxkk9X5h92VXsQ34,284195
54
56
  sourcecode/ris.py,sha256=aG2D4159gtpg968yD3PylYbIXhGFOwIlFBI0DSr84yY,20412
55
57
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
56
58
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
@@ -59,16 +61,16 @@ sourcecode/security_config.py,sha256=_m8oQAy2gP7Ho2I-IIMTFFjFVWbJIGlLEOiAWK2TDjs
59
61
  sourcecode/security_posture.py,sha256=CAJw4oJD0aAW2yRewvd_fonkzi9_HpUjBZlIDZle00s,26034
60
62
  sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
61
63
  sourcecode/semantic_impact_engine.py,sha256=HpgjenY6DLPHPomP7Hw850zA-EBokf1S4O2sh8NegdI,20351
62
- sourcecode/semantic_integration_engine.py,sha256=cUJFZaBfgzsYpj9hRa9CZjo3U9VSfIQV1k2SQYM5HZw,14524
64
+ sourcecode/semantic_integration_engine.py,sha256=rxoCuP-uM_Y4-ELeBN0sz1a88e2eI1XTJDgsS57IOLI,15513
63
65
  sourcecode/semantic_services.py,sha256=AO_p7PRfjc-AUh6ThvLMfm4iUGi0issL_frCf8sxGlI,10637
64
- sourcecode/serializer.py,sha256=_3aLX3iFbHlqVDx_IlmOBQUI6QLPrL_XX2jxF6gL_No,129094
66
+ sourcecode/serializer.py,sha256=FmQo7eojA_z3ptLW-Vgze5mrEJ6FabcZqiwbQWWTgFI,130056
65
67
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
66
68
  sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
67
69
  sourcecode/spring_impact.py,sha256=1Eu1vkdwTVsw92iBEJDe_i_FaSf4uGH9Rb0eSgIAAZc,57542
68
70
  sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
69
71
  sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
70
- sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM,13307
71
- sourcecode/spring_tx_analyzer.py,sha256=iH9mK0Tgvhgy_Ee2r13KA_RMf8HKN-pjCKQGuyCCBjM,33812
72
+ sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
73
+ sourcecode/spring_tx_analyzer.py,sha256=QMXkxa_hrqDVN3D8WJsd0nT7vfdZCT1E8ZWNoFFbvgU,34692
72
74
  sourcecode/summarizer.py,sha256=ByfFxIGVRrGrf3paQPuDOg1rVj78LyzXN_EXBkfbCLU,23582
73
75
  sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
74
76
  sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
@@ -85,7 +87,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
85
87
  sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
86
88
  sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
87
89
  sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
88
- sourcecode/detectors/java.py,sha256=xlp4mqd26QWJf3MWqmTbD0IND_VEVzPHlGaLC4MNXNA,39028
90
+ sourcecode/detectors/java.py,sha256=rTeVIKIliElE1yPdIkll1pKqDUzXD1EFFJdXxp24gr4,41005
89
91
  sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
90
92
  sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
91
93
  sourcecode/detectors/parsers.py,sha256=ug9K31tyHqinmv0HkIVQVjdTZpBv67FYKAEf52YXOSM,3178
@@ -113,8 +115,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
113
115
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
114
116
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
115
117
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
116
- sourcecode-2.1.0.dist-info/METADATA,sha256=YcJphSnTG4SGmAeb7R2XWhj4nIp4-1hazejT7QdPo9Y,10798
117
- sourcecode-2.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
118
- sourcecode-2.1.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
119
- sourcecode-2.1.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
120
- sourcecode-2.1.0.dist-info/RECORD,,
118
+ sourcecode-2.2.0.dist-info/METADATA,sha256=GWUjIL4RNPYx8cMKSRHxzELL49K0anIx6qH8aHAsB3I,10837
119
+ sourcecode-2.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
120
+ sourcecode-2.2.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
121
+ sourcecode-2.2.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
122
+ sourcecode-2.2.0.dist-info/RECORD,,