sourcecode 2.5.13__py3-none-any.whl → 2.5.16__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.5.13"
7
+ __version__ = "2.5.16"
@@ -967,25 +967,26 @@ def _java_contract_from_graph(rel_path: str, graph: "Any") -> FileContract:
967
967
  nodes/edges instead of re-parsing source. Method signatures are the graph's
968
968
  canonical compact form (`(Type)->Return`); annotations are node-grounded.
969
969
  """
970
- file_syms = [s for s in graph.symbols() if s.source_file == rel_path]
970
+ file_syms = graph.symbols_in_file(rel_path)
971
971
  type_syms = [s for s in file_syms if s.kind in ("class", "interface", "enum", "annotation")]
972
972
  type_fqns = {s.fqn for s in type_syms}
973
973
 
974
974
  # extends / implements edges out of this file's types (targets are simple
975
975
  # names for out-of-repo supertypes, resolved FQNs for in-repo ones — the
976
- # same forms the old regex captured for the unresolved case).
976
+ # same forms the old regex captured for the unresolved case). Only edges
977
+ # sourced at this file's types matter, so index straight to them instead of
978
+ # scanning the whole-repo relation set once per file.
977
979
  extends_of: dict[str, list[str]] = {}
978
980
  implements_of: dict[str, list[str]] = {}
979
981
  import_targets: set[str] = set()
980
- for r in graph.relations():
981
- if r.source not in type_fqns:
982
- continue
983
- if r.kind == "extends":
984
- extends_of.setdefault(r.source, []).append(r.target)
985
- elif r.kind == "implements":
986
- implements_of.setdefault(r.source, []).append(r.target)
987
- elif r.kind == "imports":
988
- import_targets.add(r.target)
982
+ for fqn in type_fqns:
983
+ for r in graph.relations_from(fqn):
984
+ if r.kind == "extends":
985
+ extends_of.setdefault(r.source, []).append(r.target)
986
+ elif r.kind == "implements":
987
+ implements_of.setdefault(r.source, []).append(r.target)
988
+ elif r.kind == "imports":
989
+ import_targets.add(r.target)
989
990
 
990
991
  exports: list[ExportRecord] = []
991
992
  types: list[TypeDefinition] = []
@@ -398,9 +398,17 @@ def ir_dict_to_canonical(
398
398
  # real REST surface. The CIR endpoint list feeds `spring-audit` (endpoints_analyzed),
399
399
  # `impact` and `validation`; it must apply the SAME filter so its count reconciles
400
400
  # with `endpoints` instead of being silently inflated by those pseudo-routes.
401
+ # Endpoints declared in a test-fixture / test-utility module are tagged
402
+ # scope="test_util" on route_surface by build_repo_ir. They are excluded from the
403
+ # canonical endpoint list for the same reason as the FQN filter above: the CIR
404
+ # surface feeds impact-chain, pr-impact, review-pr and spring-audit, and test
405
+ # scaffolding is not production HTTP exposure. route_surface itself keeps them
406
+ # (raw evidence); only this canonical projection drops them.
401
407
  _seen_ids: set[str] = set()
402
408
  raw_endpoints: list[CanonicalEndpoint] = []
403
409
  for r in route_surface:
410
+ if r.get("scope") == "test_util":
411
+ continue
404
412
  if _FQN_PATH_RE.search(r.get("path", "") or ""):
405
413
  continue
406
414
  ep = _route_to_canonical_endpoint(r)
sourcecode/classifier.py CHANGED
@@ -37,6 +37,26 @@ _WEB_FRAMEWORKS = {"Next.js", "React", "Vue", "Svelte", "Vite", "Flutter", "Phoe
37
37
  _CLI_FRAMEWORKS = {"Typer", "Cobra", "Clap"}
38
38
  _API_STACKS = {"python", "go", "java", "php", "ruby", "dotnet", "kotlin", "scala"}
39
39
 
40
+ # Source-file extensions per stack — single table shared by the confidence
41
+ # enrichment (_count_extension_hits) and the primary-stack evidence tiebreaker
42
+ # (select_primary_stack). Kept in one place so the two never diverge.
43
+ _STACK_EXTENSIONS: dict[str, tuple[str, ...]] = {
44
+ "python": (".py",),
45
+ "nodejs": (".js", ".ts", ".tsx"),
46
+ "go": (".go",),
47
+ "rust": (".rs",),
48
+ "java": (".java",),
49
+ "php": (".php",),
50
+ "ruby": (".rb",),
51
+ "dart": (".dart",),
52
+ "dotnet": (".cs", ".fs"),
53
+ "elixir": (".ex", ".exs"),
54
+ "kotlin": (".kt", ".kts"),
55
+ "scala": (".scala",),
56
+ "terraform": (".tf",),
57
+ "cpp": (".c", ".cc", ".cpp", ".cxx", ".hpp", ".h"),
58
+ }
59
+
40
60
  # Center-of-gravity gate for the "api" decision (neo4j field test).
41
61
  # The historical rule was pure PRESENCE: any API framework on the classpath →
42
62
  # project_type="api". That mislabels a large engine/platform that merely exposes a
@@ -63,6 +83,59 @@ _API_SURFACE_ENTRY_KINDS = frozenset({
63
83
  ConfidenceLevel = Literal["high", "medium", "low"]
64
84
 
65
85
 
86
+ def _primary_ext_hits(stack_name: str, file_paths: Sequence[str]) -> int:
87
+ exts = _STACK_EXTENSIONS.get(stack_name, ())
88
+ if not exts:
89
+ return 0
90
+ return sum(1 for p in file_paths if p.endswith(exts))
91
+
92
+
93
+ def select_primary_stack(
94
+ stacks: Sequence[StackDetection],
95
+ project_type: str | None,
96
+ file_paths: Sequence[str] | None = None,
97
+ ) -> list[StackDetection]:
98
+ """Single authority for which stack is primary.
99
+
100
+ Marks exactly ONE stack instance ``primary=True`` (all others ``False``) on the
101
+ FINAL assembled stack set (root + workspace stacks). Selection order:
102
+ ``(priority, confidence, manifest, source-file volume)``. The volume component is
103
+ a pure tiebreaker — it decides only when the prior three tie (e.g. a polyglot repo
104
+ where a Java and a .NET stack are both manifest-detected API stacks), so a 1-file
105
+ .NET example never outranks 86 Java files. Being last, it never reorders stacks
106
+ that already differ on the earlier components.
107
+
108
+ This is the ONLY place a primary decision is made. Every consumer must read
109
+ ``stack.primary`` — none may re-select or invent a ``stacks[0]`` fallback.
110
+ """
111
+ stack_list = list(stacks)
112
+ if not stack_list:
113
+ return stack_list
114
+ paths = list(file_paths or [])
115
+
116
+ def sort_key(stack: StackDetection) -> tuple[int, int, int, int]:
117
+ score = {"low": 0, "medium": 1, "high": 2}.get(stack.confidence, 0)
118
+ manifest_weight = 1 if stack.manifests else 0
119
+ priority = 0
120
+ # Backend server-side stacks with manifest evidence outrank frontend stacks
121
+ # in fullstack projects (e.g. Java+Spring wins over nodejs+Angular).
122
+ if (project_type in {"fullstack", "api"}
123
+ and stack.stack in _API_STACKS
124
+ and stack.manifests):
125
+ priority = 4
126
+ elif (project_type in {"webapp", "fullstack"} and stack.stack in {"nodejs", "elixir"}
127
+ or project_type == "api" and stack.stack in _API_STACKS
128
+ or project_type == "cli" and any(
129
+ framework.name in _CLI_FRAMEWORKS for framework in stack.frameworks
130
+ )
131
+ or project_type == "cli" and stack.stack in {"cpp", "dotnet"}):
132
+ priority = 3
133
+ return (priority, score, manifest_weight, _primary_ext_hits(stack.stack, paths))
134
+
135
+ winner = max(stack_list, key=sort_key)
136
+ return [replace(s, primary=(s is winner)) for s in stack_list]
137
+
138
+
66
139
  class TypeClassifier:
67
140
  """Clasifica project_type y enriquece stacks con confianza/primary."""
68
141
 
@@ -74,15 +147,12 @@ class TypeClassifier:
74
147
  ) -> tuple[list[StackDetection], str | None]:
75
148
  enriched = [self._enrich_stack(file_tree, stack, entry_points) for stack in stacks]
76
149
  project_type = self._classify_project_type(file_tree, enriched, entry_points)
77
- primary_stack = self._select_primary_stack(enriched, project_type)
78
-
79
- final_stacks: list[StackDetection] = []
80
- primary_assigned = False
81
- for stack in enriched:
82
- is_primary = stack.stack == primary_stack and not primary_assigned
83
- if is_primary:
84
- primary_assigned = True
85
- final_stacks.append(replace(stack, primary=is_primary))
150
+ # Delegate to the single primary-stack authority. enrich only ever sees the
151
+ # ROOT stacks; the authority runs again at model finalization over the full
152
+ # root+workspace set (that call is the one consumers read).
153
+ final_stacks = select_primary_stack(
154
+ enriched, project_type, flatten_file_tree(file_tree)
155
+ )
86
156
  return final_stacks, project_type
87
157
 
88
158
  def _enrich_stack(
@@ -371,50 +441,8 @@ class TypeClassifier:
371
441
  has_api = True
372
442
  return has_web and has_api
373
443
 
374
- def _select_primary_stack(
375
- self, stacks: Sequence[StackDetection], project_type: str | None
376
- ) -> str | None:
377
- if not stacks:
378
- return None
379
-
380
- def sort_key(stack: StackDetection) -> tuple[int, int, int]:
381
- score = {"low": 0, "medium": 1, "high": 2}.get(stack.confidence, 0)
382
- manifest_weight = 1 if stack.manifests else 0
383
- priority = 0
384
- # Backend server-side stacks with manifest evidence outrank frontend stacks
385
- # in fullstack projects (e.g. Java+Spring wins over nodejs+Angular).
386
- if (project_type in {"fullstack", "api"}
387
- and stack.stack in _API_STACKS
388
- and stack.manifests):
389
- priority = 4
390
- elif (project_type in {"webapp", "fullstack"} and stack.stack in {"nodejs", "elixir"}
391
- or project_type == "api" and stack.stack in _API_STACKS
392
- or project_type == "cli" and any(
393
- framework.name in _CLI_FRAMEWORKS for framework in stack.frameworks
394
- )
395
- or project_type == "cli" and stack.stack in {"cpp", "dotnet"}):
396
- priority = 3
397
- return (priority, score, manifest_weight)
398
-
399
- return max(stacks, key=sort_key).stack
400
-
401
444
  def _count_extension_hits(self, file_tree: dict[str, Any], stack: str) -> int:
402
- extensions = {
403
- "python": (".py",),
404
- "nodejs": (".js", ".ts", ".tsx"),
405
- "go": (".go",),
406
- "rust": (".rs",),
407
- "java": (".java",),
408
- "php": (".php",),
409
- "ruby": (".rb",),
410
- "dart": (".dart",),
411
- "dotnet": (".cs", ".fs"),
412
- "elixir": (".ex", ".exs"),
413
- "kotlin": (".kt", ".kts"),
414
- "scala": (".scala",),
415
- "terraform": (".tf",),
416
- "cpp": (".c", ".cc", ".cpp", ".cxx", ".hpp", ".h"),
417
- }.get(stack, ())
445
+ extensions = _STACK_EXTENSIONS.get(stack, ())
418
446
  return sum(1 for path in flatten_file_tree(file_tree) if path.endswith(extensions))
419
447
 
420
448
  def _score_to_confidence(self, score: int) -> ConfidenceLevel:
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 import perf
18
19
  from sourcecode.caller_metrics import CALLER_METRIC_RECONCILIATION
19
20
  from sourcecode.repository_ir import extract_java_endpoints as _extract_java_endpoints
20
21
 
@@ -1703,6 +1704,7 @@ def main(
1703
1704
  _progress = Progress()
1704
1705
  _progress.start("scanning files")
1705
1706
 
1707
+ _perf_discovery = perf.start()
1706
1708
  scanner = AdaptiveScanner(target, topology=_topology, base_depth=effective_depth,
1707
1709
  extra_excludes=_extra_excludes)
1708
1710
  raw_tree = scanner.scan_tree()
@@ -1710,6 +1712,8 @@ def main(
1710
1712
  _progress.update("parsing manifests")
1711
1713
  # 2. Filter .env and *.secret entries from file tree (SEC-02, all levels)
1712
1714
  file_tree = filter_sensitive_files(raw_tree, redactor)
1715
+ perf.stop("discovery", _perf_discovery)
1716
+ _perf_detection = perf.start()
1713
1717
  detector = ProjectDetector(build_default_detectors())
1714
1718
  workspace_analysis = WorkspaceAnalyzer().analyze(target, manifests)
1715
1719
 
@@ -1951,6 +1955,8 @@ def main(
1951
1955
  else None
1952
1956
  )
1953
1957
 
1958
+ perf.stop("detection", _perf_detection)
1959
+ _perf_analysis = perf.start()
1954
1960
  # 3. Build schema
1955
1961
  # Compute analyzer fingerprints: short hashes of each analyzer's key rule
1956
1962
  # constants so that a rule change is always visible in the output, regardless
@@ -1965,6 +1971,13 @@ def main(
1965
1971
  analyzer_fingerprints=_fingerprints,
1966
1972
  traversal_topology=_topology.as_dict(),
1967
1973
  )
1974
+ # Single primary-stack authority over the FINAL assembled set (root + workspace
1975
+ # stacks). enrich marked the root stacks; workspace stacks were appended after,
1976
+ # so re-run the one authority here — idempotent — so every consumer of sm.stacks
1977
+ # reads a coherent primary flag.
1978
+ from sourcecode.classifier import select_primary_stack as _select_primary_stack
1979
+ from sourcecode.tree_utils import flatten_file_tree as _flatten_ft
1980
+ stacks = _select_primary_stack(stacks, project_type, _flatten_ft(file_tree))
1968
1981
  sm = SourceMap(
1969
1982
  metadata=metadata,
1970
1983
  file_tree=file_tree,
@@ -2404,8 +2417,10 @@ def main(
2404
2417
  err=True,
2405
2418
  )
2406
2419
 
2420
+ perf.stop("analysis", _perf_analysis)
2407
2421
  # 4. Serialize
2408
2422
  _progress.update("serializing output")
2423
+ _perf_serialize = perf.start()
2409
2424
  if _is_contract_mode and not agent:
2410
2425
  from sourcecode.serializer import contract_view as _contract_view
2411
2426
  _depth = _CONTRACT_DEPTH.get(mode, "minimal")
@@ -2448,6 +2463,9 @@ def main(
2448
2463
  raw_dict = redact_dict(raw_dict)
2449
2464
  content = _serialize_dict(raw_dict, format)
2450
2465
 
2466
+ perf.stop("serialize", _perf_serialize)
2467
+ perf.flush_recorder()
2468
+
2451
2469
  # 5. Telemetry (fire-and-forget, never blocks)
2452
2470
  try:
2453
2471
  from sourcecode import telemetry as _tel
@@ -408,6 +408,7 @@ class ContextGraph:
408
408
  "_cir", "_nodes_by_fqn", "_nodes", "_build_ms",
409
409
  "_body_index", "_literal_index", "_guard_index", "_span_index",
410
410
  "_class_type_index", "_relations_all", "_symbols_sorted",
411
+ "_symbols_by_file", "_relations_by_source",
411
412
  )
412
413
 
413
414
  def __init__(self, cir: CanonicalRepositoryIR, *, build_ms: float = 0.0) -> None:
@@ -439,6 +440,12 @@ class ContextGraph:
439
440
  # filtered cheaply thereafter.
440
441
  self._relations_all: Optional[tuple[Relation, ...]] = None
441
442
  self._symbols_sorted: Optional[tuple[Symbol, ...]] = None
443
+ # Per-key groupings for per-file consumers (`_java_contract_from_graph`
444
+ # runs once per file). Without these, each file filtered the whole-repo
445
+ # symbol/relation set — the residual O(files × total) scan that the
446
+ # memoized projections above did not remove. Built once, O(1) lookup.
447
+ self._symbols_by_file: Optional[dict[str, list[Symbol]]] = None
448
+ self._relations_by_source: Optional[dict[str, list[Relation]]] = None
442
449
 
443
450
  # -- construction -------------------------------------------------------
444
451
 
@@ -518,6 +525,34 @@ class ContextGraph:
518
525
  and (name_contains is None or name_contains in s.fqn)
519
526
  ]
520
527
 
528
+ def symbols_in_file(self, source_file: str) -> list[Symbol]:
529
+ """All symbols declared in one file, FQN-sorted — the same order and set
530
+ a no-filter ``symbols()`` scan filtered to this file would produce, but
531
+ O(1) after a one-time O(total) grouping instead of O(total) per call.
532
+ """
533
+ if self._symbols_by_file is None:
534
+ if self._symbols_sorted is None:
535
+ self._symbols_sorted = tuple(sorted(self._nodes, key=lambda s: s.fqn))
536
+ idx: dict[str, list[Symbol]] = {}
537
+ for s in self._symbols_sorted:
538
+ idx.setdefault(s.source_file, []).append(s)
539
+ self._symbols_by_file = idx
540
+ return self._symbols_by_file.get(source_file, [])
541
+
542
+ def relations_from(self, source_fqn: str) -> list[Relation]:
543
+ """All relations whose source is ``source_fqn``, in the IR's stable edge
544
+ order. O(1) after a one-time O(edges) grouping — replaces per-file scans
545
+ of the whole relation set.
546
+ """
547
+ if self._relations_by_source is None:
548
+ if self._relations_all is None:
549
+ self._relations_all = tuple(_edge_to_relation(e) for e in self._cir.call_graph)
550
+ idx: dict[str, list[Relation]] = {}
551
+ for r in self._relations_all:
552
+ idx.setdefault(r.source, []).append(r)
553
+ self._relations_by_source = idx
554
+ return self._relations_by_source.get(source_fqn, [])
555
+
521
556
  def types(self) -> list[Symbol]:
522
557
  """All class/interface/enum/annotation symbols, sorted by FQN."""
523
558
  return sorted(
@@ -67,6 +67,16 @@ _BOOTSTRAP_ANNOTATION_RE = re.compile(
67
67
  r'EnableAutoConfiguration|SpringBootConfiguration)\b'
68
68
  r'|\bextends\s+SpringBootServletInitializer\b'
69
69
  )
70
+ # Comment strippers — a main()/bootstrap annotation inside a javadoc example or a
71
+ # comment is NOT an entry point (e.g. Broadleaf's *AutoConfiguration classes whose
72
+ # javadoc shows `public static void main` and mentions @SpringBootApplication).
73
+ # Detection of bootstrap entries must run on de-commented source.
74
+ _BLOCK_COMMENT_RE = re.compile(r'/\*.*?\*/', re.DOTALL)
75
+ _LINE_COMMENT_RE = re.compile(r'//[^\n]*')
76
+
77
+
78
+ def _strip_java_comments(source: str) -> str:
79
+ return _LINE_COMMENT_RE.sub(' ', _BLOCK_COMMENT_RE.sub(' ', source))
70
80
 
71
81
  # --- Keycloak / Quarkus SPI ---
72
82
  # Matches "implements EventListenerProvider" or "implements Foo, EventListenerProvider, Bar"
@@ -533,12 +543,21 @@ class JavaDetector(AbstractDetector):
533
543
  _other_files = [p for p in _non_test if "Controller" not in p]
534
544
  scan_candidates = _ctrl_files + _other_files[:max(0, _MAX_JAVA_ENTRY_SCAN - len(_ctrl_files))]
535
545
 
546
+ # Paths already emitted as application entries by the name-gated step-1 scan,
547
+ # so the annotation scan (which now also recovers main()-bearing files) does
548
+ # not list the same bootstrap twice.
549
+ _app_paths = {ep.path for ep in entry_points if ep.kind == "application"}
536
550
  annotation_eps: list[EntryPoint] = []
537
551
  for rel_path in scan_candidates:
538
552
  if len(annotation_eps) >= _MAX_ANNOTATION_ENTRY_POINTS:
539
553
  break
540
554
  abs_path = context.root / rel_path
541
- annotation_eps.extend(self._scan_java_file_for_entry_points(abs_path, rel_path))
555
+ for _ep in self._scan_java_file_for_entry_points(abs_path, rel_path):
556
+ if _ep.kind == "application":
557
+ if _ep.path in _app_paths:
558
+ continue
559
+ _app_paths.add(_ep.path)
560
+ annotation_eps.append(_ep)
542
561
  entry_points.extend(annotation_eps)
543
562
 
544
563
  # 3. web.xml servlet/filter declarations
@@ -611,6 +630,7 @@ class JavaDetector(AbstractDetector):
611
630
  content = abs_path.read_text(encoding="utf-8", errors="replace")
612
631
  except OSError:
613
632
  return None
633
+ content = _strip_java_comments(content)
614
634
  if _BOOTSTRAP_ANNOTATION_RE.search(content):
615
635
  return "bootstrap_annotation"
616
636
  if _MAIN_METHOD_RE.search(content):
@@ -678,6 +698,20 @@ class JavaDetector(AbstractDetector):
678
698
  and "ControllerAdvice" not in content
679
699
  and not any(ann in content for ann in _CUSTOM_SECURITY_ANNOTATIONS)
680
700
  and not has_jax_rs and not has_cdi and not has_spi):
701
+ # Not a controller/security/DI file — but it may still be an application
702
+ # entry point. The name-gated step-1 scan only reads *Application.java /
703
+ # *Main.java, so a real main(String[]) in a differently-named class (e.g.
704
+ # neo4j's Neo4jCommunity.java server bootstrap) would otherwise be invisible,
705
+ # leaving a client CLI named Main.java as the sole reported bootstrap. Reuse
706
+ # the content already read here; strip comments first so a main()/annotation
707
+ # in a javadoc example is never mistaken for a real entry point.
708
+ _decommented = _strip_java_comments(content)
709
+ if _BOOTSTRAP_ANNOTATION_RE.search(_decommented):
710
+ return [EntryPoint(path=rel_path, stack="java", kind="application",
711
+ source="bootstrap_annotation")]
712
+ if _MAIN_METHOD_RE.search(_decommented):
713
+ return [EntryPoint(path=rel_path, stack="java", kind="application",
714
+ source="main_method")]
681
715
  return []
682
716
 
683
717
  if _REST_CONTROLLER_RE.search(content):
@@ -30,7 +30,10 @@ ENDPOINT_SURFACE_RECONCILIATION: str = (
30
30
  "handler after inheritance projection and JAX-RS sub-resource-locator composition. "
31
31
  "It is NOT the canonical API surface — it still contains framework dynamic-admin "
32
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 "
33
+ "`endpoints` command excludes, and endpoints from test-fixture / test-utility "
34
+ "modules (tagged scope=\"test_util\"), which the canonical CIR surface excludes so "
35
+ "impact-chain / pr-impact / spring-audit do not report test scaffolding as "
36
+ "production HTTP exposure. Endpoint counts differ across commands BY DESIGN "
34
37
  "and are not contradictory; expected ordering for one repo is "
35
38
  "repo-ir.route_surface (raw, includes FQN-shaped framework routes) >= "
36
39
  "endpoints.total (canonical REST/API surface, FQN-shaped routes filtered out) >= "
@@ -168,6 +168,37 @@ def is_test_fixture_module_path(path: str) -> bool:
168
168
  return False
169
169
 
170
170
 
171
+ _EXAMPLE_MODULE_TOKENS = frozenset({
172
+ "example", "examples", "demo", "demos", "sample", "samples", "showcase",
173
+ })
174
+
175
+
176
+ def is_example_module_path(path: str) -> bool:
177
+ """Return True when *path* is inside an example/demo MODULE directory.
178
+
179
+ Structural: a directory segment above the source root carries a hyphen/underscore
180
+ -delimited example/demo token (e.g. ``eureka-examples/src/main/java/...``). Package
181
+ components below the source root (``io/confluent/examples/...``) are NOT matched —
182
+ only a module directory is a functional role; a package of the same name is not.
183
+ """
184
+ norm = path.replace("\\", "/").lower()
185
+ # Prepend "/" so a relative path ("src/main/...") matches the same boundary as an
186
+ # absolute one; without this the head-truncation silently failed and package
187
+ # components below the source root (e.g. ".../samples/petclinic/...") false-matched.
188
+ probe = "/" + norm
189
+ head = norm
190
+ for root in ("/src/main/", "/src/test/", "/src/"):
191
+ idx = probe.find(root)
192
+ if idx >= 0:
193
+ head = probe[:idx]
194
+ break
195
+ for seg in head.split("/"):
196
+ toks = seg.replace("_", "-").split("-")
197
+ if any(t in _EXAMPLE_MODULE_TOKENS for t in toks):
198
+ return True
199
+ return False
200
+
201
+
171
202
  def is_vendor_path(path: str) -> bool:
172
203
  """Return True when *path* is inside a vendored / third-party directory.
173
204
 
sourcecode/perf.py ADDED
@@ -0,0 +1,268 @@
1
+ """Performance instrumentation and baseline schema — see ADR-0006.
2
+
3
+ Two responsibilities, kept separate:
4
+
5
+ * An **in-process recorder** that times the frozen phase taxonomy (§3). It is
6
+ *zero-cost when disabled* (ADR-0006 §5.5): with ``SOURCECODE_PERF`` unset,
7
+ :func:`span` yields without reading a clock, allocating a span, or writing a
8
+ dict. A production ``ask`` invocation pays nothing for the harness existing.
9
+
10
+ * **Pure functions** — percentiles and the ``perf-baseline-v1`` cell builder —
11
+ that the fleet harness (``scripts/perf_harness.py``) uses to turn a set of raw
12
+ per-run measurements into one frozen-schema artifact.
13
+
14
+ The phase taxonomy and the output schema are the frozen contract; this module is
15
+ the replaceable collector.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import platform
24
+ import socket
25
+ import sys
26
+ import time
27
+ from contextlib import contextmanager
28
+ from datetime import datetime, timezone
29
+ from typing import Iterator, Mapping, Sequence
30
+
31
+ # ── Frozen contract constants (ADR-0006) ────────────────────────────────────
32
+
33
+ SCHEMA_VERSION = "perf-baseline-v1"
34
+
35
+ #: The frozen phase taxonomy. Order is contractual — see ADR-0006 §3.
36
+ #: These are the pipeline's *measured* contiguous seams, not compiler-shaped
37
+ #: guesses: repo discovery, what-is-this-repo detection+enrichment, model
38
+ #: assembly + semantic/contract extraction, and output rendering.
39
+ PHASES: tuple[str, ...] = ("discovery", "detection", "analysis", "serialize")
40
+
41
+ #: Env var that arms the in-process recorder.
42
+ ENV_ENABLE = "SOURCECODE_PERF"
43
+ #: Env var naming the JSONL file the recorder appends its phase timings to.
44
+ ENV_LOG = "SOURCECODE_PERF_LOG"
45
+
46
+
47
+ # ── In-process recorder (zero-cost when disabled) ───────────────────────────
48
+
49
+
50
+ class _Recorder:
51
+ """Accumulates elapsed milliseconds per named phase for one process."""
52
+
53
+ __slots__ = ("_spans",)
54
+
55
+ def __init__(self) -> None:
56
+ self._spans: dict[str, float] = {}
57
+
58
+ def add(self, name: str, ms: float) -> None:
59
+ self._spans[name] = self._spans.get(name, 0.0) + ms
60
+
61
+ def as_dict(self) -> dict[str, float]:
62
+ return dict(self._spans)
63
+
64
+ def flush(self, path: str) -> None:
65
+ """Append the accumulated phase timings as one JSONL record."""
66
+ with open(path, "a", encoding="utf-8") as fh:
67
+ fh.write(json.dumps({"phase_ms": self._spans}) + "\n")
68
+
69
+
70
+ def _resolve_recorder() -> _Recorder | None:
71
+ """Return a recorder iff the environment arms it, else ``None``.
72
+
73
+ Resolved once and cached. Kept out of :func:`span`'s hot path so the
74
+ disabled case is a single ``is None`` check.
75
+ """
76
+ if _resolve_recorder._cached is not _UNSET: # type: ignore[attr-defined]
77
+ return _resolve_recorder._cached # type: ignore[attr-defined]
78
+ rec = _Recorder() if os.environ.get(ENV_ENABLE) == "1" else None
79
+ _resolve_recorder._cached = rec # type: ignore[attr-defined]
80
+ return rec
81
+
82
+
83
+ _UNSET = object()
84
+ _resolve_recorder._cached = _UNSET # type: ignore[attr-defined]
85
+
86
+
87
+ @contextmanager
88
+ def span(name: str) -> Iterator[None]:
89
+ """Time a phase region. No-op (and clock-free) when perf is disabled.
90
+
91
+ ``name`` should be one of :data:`PHASES`, but any name is accepted so the
92
+ caller is never coupled to import order; unknown names simply won't map into
93
+ a baseline cell.
94
+ """
95
+ rec = _resolve_recorder()
96
+ if rec is None:
97
+ # Zero-cost path: no perf_counter, no object, no dict write.
98
+ yield
99
+ return
100
+ t0 = time.perf_counter()
101
+ try:
102
+ yield
103
+ finally:
104
+ rec.add(name, (time.perf_counter() - t0) * 1000.0)
105
+
106
+
107
+ def start() -> float | None:
108
+ """Open a phase measurement without indenting the region it spans.
109
+
110
+ Pairs with :func:`stop`. Returns a start timestamp, or ``None`` when perf is
111
+ disabled — in which case no clock is read. Use this instead of :func:`span`
112
+ to instrument a long, branchy region in place without wrapping it in a
113
+ ``with`` block (which would force a re-indent of hundreds of lines).
114
+ """
115
+ return time.perf_counter() if _resolve_recorder() is not None else None
116
+
117
+
118
+ def stop(name: str, t0: float | None) -> None:
119
+ """Close a :func:`start` measurement, accumulating into phase ``name``.
120
+
121
+ A no-op when perf is disabled (``t0 is None``). Because the recorder sums by
122
+ name, the same phase may be opened and closed at several scattered call
123
+ sites and the times add up.
124
+ """
125
+ if t0 is None:
126
+ return
127
+ rec = _resolve_recorder()
128
+ if rec is not None:
129
+ rec.add(name, (time.perf_counter() - t0) * 1000.0)
130
+
131
+
132
+ def flush_recorder() -> None:
133
+ """Write accumulated phase timings to ``$SOURCECODE_PERF_LOG`` if armed."""
134
+ rec = _resolve_recorder()
135
+ path = os.environ.get(ENV_LOG)
136
+ if rec is not None and path:
137
+ rec.flush(path)
138
+
139
+
140
+ def _reset_for_tests() -> None:
141
+ """Clear the cached recorder so tests can re-resolve after mutating env."""
142
+ _resolve_recorder._cached = _UNSET # type: ignore[attr-defined]
143
+
144
+
145
+ # ── Pure statistics ─────────────────────────────────────────────────────────
146
+
147
+
148
+ def percentile(values: Sequence[float], q: float) -> float:
149
+ """Linear-interpolated percentile, ``q`` in ``[0, 100]``.
150
+
151
+ ``percentile(v, 50)`` is the median. Used for both p50 (central tendency —
152
+ never the mean, ADR-0006 §4) and p95 (tail).
153
+ """
154
+ if not values:
155
+ raise ValueError("percentile of empty sequence")
156
+ if not 0.0 <= q <= 100.0:
157
+ raise ValueError(f"q out of range: {q}")
158
+ s = sorted(values)
159
+ if len(s) == 1:
160
+ return float(s[0])
161
+ rank = (q / 100.0) * (len(s) - 1)
162
+ lo = int(rank)
163
+ hi = min(lo + 1, len(s) - 1)
164
+ frac = rank - lo
165
+ return float(s[lo] + (s[hi] - s[lo]) * frac)
166
+
167
+
168
+ def p50(values: Sequence[float]) -> float:
169
+ return percentile(values, 50.0)
170
+
171
+
172
+ def p95(values: Sequence[float]) -> float:
173
+ return percentile(values, 95.0)
174
+
175
+
176
+ def _stat_block(values: Sequence[float], *, tail: bool = True) -> dict[str, float]:
177
+ block = {"p50": round(p50(values), 3), "p95": round(p95(values), 3)}
178
+ if not tail:
179
+ block.pop("p95")
180
+ return block
181
+
182
+
183
+ # ── Environment fingerprint ─────────────────────────────────────────────────
184
+
185
+
186
+ def collect_env() -> dict[str, object]:
187
+ """Capture the fields that make a baseline comparable only to same-host runs.
188
+
189
+ ``hostname_hash`` (not the raw hostname) groups same-machine runs without
190
+ leaking the machine name. Cross-host absolute numbers are recorded, never
191
+ diffed (ADR-0006 §5.4).
192
+ """
193
+ try:
194
+ host = socket.gethostname()
195
+ except OSError:
196
+ host = "unknown"
197
+ return {
198
+ "python": platform.python_version(),
199
+ "os": sys.platform,
200
+ "cpu": platform.processor() or platform.machine() or "unknown",
201
+ "cores": os.cpu_count() or 0,
202
+ "hostname_hash": hashlib.sha256(host.encode("utf-8")).hexdigest()[:8],
203
+ }
204
+
205
+
206
+ # ── perf-baseline-v1 cell builder ───────────────────────────────────────────
207
+
208
+
209
+ def build_baseline_cell(
210
+ *,
211
+ tool_version: str,
212
+ command: str,
213
+ repo: Mapping[str, object],
214
+ mode: str,
215
+ wall_ms_runs: Sequence[float],
216
+ rss_bytes_runs: Sequence[float],
217
+ phase_ms_runs: Sequence[Mapping[str, float]] | None = None,
218
+ env: Mapping[str, object] | None = None,
219
+ captured_at: str | None = None,
220
+ ) -> dict:
221
+ """Assemble one frozen-schema ``perf-baseline-v1`` cell from raw runs.
222
+
223
+ ``wall_ms_runs`` / ``rss_bytes_runs`` hold one value per run. ``phase_ms_runs``
224
+ is one ``{phase: ms}`` mapping per run; when absent, phase timings are emitted
225
+ as ``null`` with an honest ``instrumentation: "absent"`` marker rather than a
226
+ fabricated zero.
227
+ """
228
+ if not wall_ms_runs:
229
+ raise ValueError("wall_ms_runs must be non-empty")
230
+
231
+ phase_ms: dict[str, object] = {}
232
+ if phase_ms_runs:
233
+ for ph in PHASES:
234
+ series = [float(r[ph]) for r in phase_ms_runs if ph in r]
235
+ phase_ms[ph] = _stat_block(series) if series else None
236
+ # Un-attributed time must stay visible: total minus the sum of phases,
237
+ # never hidden inside a phase (ADR-0006 §3).
238
+ unattributed = []
239
+ for i, wall in enumerate(wall_ms_runs):
240
+ if i < len(phase_ms_runs):
241
+ attributed = sum(float(v) for v in phase_ms_runs[i].values())
242
+ unattributed.append(wall - attributed)
243
+ phase_ms["unattributed_p50"] = (
244
+ round(p50(unattributed), 3) if unattributed else None
245
+ )
246
+ else:
247
+ for ph in PHASES:
248
+ phase_ms[ph] = None
249
+ phase_ms["instrumentation"] = "absent"
250
+
251
+ return {
252
+ "schema_version": SCHEMA_VERSION,
253
+ "tool_version": tool_version,
254
+ "captured_at": captured_at or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
255
+ "command": command,
256
+ "repo": dict(repo),
257
+ "mode": mode,
258
+ "runs": len(wall_ms_runs),
259
+ "wall_ms": {
260
+ "p50": round(p50(wall_ms_runs), 3),
261
+ "p95": round(p95(wall_ms_runs), 3),
262
+ "min": round(float(min(wall_ms_runs)), 3),
263
+ "max": round(float(max(wall_ms_runs)), 3),
264
+ },
265
+ "phase_ms": phase_ms,
266
+ "rss_peak_bytes": _stat_block(rss_bytes_runs) if rss_bytes_runs else None,
267
+ "env": dict(env) if env is not None else collect_env(),
268
+ }
@@ -25,7 +25,10 @@ from typing import Any, Iterable, Optional
25
25
  from sourcecode.caller_metrics import CALLER_METRIC_RECONCILIATION
26
26
  from sourcecode.endpoint_metrics import ENDPOINT_SURFACE_RECONCILIATION
27
27
  from sourcecode.fqn_utils import normalize_owner_fqn as _normalize_owner_fqn
28
- from sourcecode.path_filters import is_test_path as _is_test_path
28
+ from sourcecode.path_filters import (
29
+ is_test_path as _is_test_path,
30
+ is_test_fixture_module_path as _is_test_fixture_module_path,
31
+ )
29
32
  from sourcecode.security_config import (
30
33
  CustomSecuritySpec,
31
34
  capture_markers as _capture_markers,
@@ -4176,6 +4179,23 @@ def _assemble(
4176
4179
  _route_surface = _build_route_surface(
4177
4180
  sorted_syms, route_diffs, extends_map=_extends_map, custom_security=custom_security
4178
4181
  )
4182
+ # Tag routes that originate in a test-fixture / test-utility module (dummy web
4183
+ # services, JAX-RS @Path fixtures shipped under src/main of a *-test-utils /
4184
+ # testsuite / test-framework module). route_surface stays RAW evidence — nothing
4185
+ # is dropped here; the tag lets the CIR endpoint projection (ir_dict_to_canonical)
4186
+ # exclude them so every CIR consumer (impact-chain, pr-impact, review-pr,
4187
+ # spring-audit) shares one production-endpoint definition with the `endpoints`
4188
+ # command, while repo-ir can still see the full surface. Mirrors the scope tag
4189
+ # applied in extract_java_endpoints.
4190
+ _rs_fqn_file = {
4191
+ s.symbol: (s.source_file or s.declaring_file)
4192
+ for s in sorted_syms
4193
+ if s.type in ("class", "interface")
4194
+ }
4195
+ for _r in _route_surface:
4196
+ _rf = _rs_fqn_file.get(_r.get("effective_class", ""), "")
4197
+ if _rf and _is_test_fixture_module_path(_rf):
4198
+ _r["scope"] = "test_util"
4179
4199
  _analysis_gaps = _compute_analysis_gaps(
4180
4200
  sorted_syms, spring_summary, _route_surface, sorted_rels,
4181
4201
  unparsed_type_files=unparsed_type_files,
@@ -5098,11 +5118,23 @@ def build_repo_ir(
5098
5118
  _r["security_annotations"] = {"policy": "xml_or_filter_chain"}
5099
5119
 
5100
5120
  # L-6: inject analysis_meta — files_read, lines_read, symbols_analyzed, token_estimate
5121
+ # symbol_kinds is the deterministic per-kind breakdown; classes/methods are the
5122
+ # two rollups the perf harness consumes for cost-per-symbol (ADR-0006). The IR
5123
+ # owns the taxonomy, so the rollup lives here rather than in the harness.
5124
+ _kind_counts: dict[str, int] = {}
5125
+ for _s in all_symbols:
5126
+ _k = _s.symbol_kind or "unknown"
5127
+ _kind_counts[_k] = _kind_counts.get(_k, 0) + 1
5128
+ _TYPE_KINDS = ("class", "interface", "enum", "annotation", "record")
5129
+ _METHOD_KINDS = ("method", "constructor")
5101
5130
  ir["analysis_meta"] = {
5102
5131
  "files_read": _meta_files_read,
5103
5132
  "lines_read": _meta_lines_read,
5104
5133
  "symbols_analyzed": len(all_symbols),
5105
5134
  "token_estimate": _meta_chars_read // 4, # 4 chars ≈ 1 token (rough approximation)
5135
+ "symbol_kinds": {k: _kind_counts[k] for k in sorted(_kind_counts)},
5136
+ "classes": sum(_kind_counts.get(k, 0) for k in _TYPE_KINDS),
5137
+ "methods": sum(_kind_counts.get(k, 0) for k in _METHOD_KINDS),
5106
5138
  }
5107
5139
  if emit_body_facts:
5108
5140
  # Additive, in-memory only: not a cir_hash input, not serialized by the
@@ -6397,6 +6429,12 @@ def compute_blast_radius(
6397
6429
 
6398
6430
  endpoints_affected: list[dict] = []
6399
6431
  for ep in route_surface:
6432
+ # Test-fixture routes (tagged by build_repo_ir) are not production HTTP
6433
+ # exposure — skip them so the legacy `impact` command shares the single
6434
+ # production-endpoint definition used by the CIR surfaces. Nothing else about
6435
+ # blast-radius selection changes; FQN-admin routes are untouched.
6436
+ if ep.get("scope") == "test_util":
6437
+ continue
6400
6438
  ep_class = ep.get("effective_class") or ep.get("controller") or ep.get("class") or ""
6401
6439
  ep_symbol = ep.get("symbol") or ""
6402
6440
  ep_handler = (
sourcecode/serializer.py CHANGED
@@ -700,7 +700,11 @@ def _security_surface_from_eps(
700
700
  def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
701
701
  """Separate Java entry points into bootstrap / security / controllers groups."""
702
702
  from pathlib import Path as _Path
703
- bootstrap: list[str] = []
703
+ from sourcecode.path_filters import (
704
+ is_example_module_path,
705
+ is_test_fixture_module_path,
706
+ )
707
+ bootstrap_entries: list[tuple[str, str]] = [] # (path, source)
704
708
  security: list[str] = []
705
709
  controllers: list[dict] = []
706
710
  seen_b: set[str] = set()
@@ -720,9 +724,19 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
720
724
  # filename alone — XSD-generated Application.java model classes must not
721
725
  # appear here.
722
726
  if kind == "application":
727
+ # Benchmark/example/tool mains (classified auxiliary/development) are real
728
+ # entry points but not the app's deployment bootstrap — keep them out.
729
+ if getattr(ep, "classification", None) in ("auxiliary", "development"):
730
+ continue
731
+ # Same for mains that live in an example/demo or test-fixture MODULE. This
732
+ # filter runs here (not in the detector) because workspace sub-scans prefix
733
+ # the module name onto the path only after detection — the full path with
734
+ # the module dir is visible only at this serialization stage.
735
+ if is_example_module_path(path) or is_test_fixture_module_path(path):
736
+ continue
723
737
  if path not in seen_b:
724
738
  seen_b.add(path)
725
- bootstrap.append(path)
739
+ bootstrap_entries.append((path, getattr(ep, "source", "")))
726
740
  elif kind == "filter" or any(k in stem for k in ("Filter", "Security", "Auth", "Jwt", "WebSecurity")):
727
741
  if path not in seen_s:
728
742
  seen_s.add(path)
@@ -736,12 +750,31 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
736
750
  item["http_path"] = http_path
737
751
  controllers.append(item)
738
752
 
753
+ # Order deployment-annotation entries (@SpringBootApplication/@QuarkusMain/…) ahead
754
+ # of plain main() classes, so a repo's real bootstrap leads even when many main()
755
+ # methods exist; then cap for readability while reporting the true total. A
756
+ # multi-main repo (e.g. neo4j: cypher-shell CLI + Neo4jCommunity server + admin
757
+ # tools) surfaces its server entry and its multiplicity instead of one arbitrary pick.
758
+ bootstrap_entries.sort(key=lambda pe: 0 if pe[1] == "bootstrap_annotation" else 1)
759
+ _bootstrap_total = len(bootstrap_entries)
760
+ _BOOTSTRAP_CAP = 10
761
+ bootstrap = [p for p, _src in bootstrap_entries[:_BOOTSTRAP_CAP]]
762
+
739
763
  if not bootstrap and not security:
740
764
  return None
741
765
 
742
766
  result: dict[str, Any] = {}
743
767
  if bootstrap:
744
768
  result["bootstrap"] = bootstrap
769
+ if _bootstrap_total > len(bootstrap):
770
+ result["bootstrap_note"] = (
771
+ f"{_bootstrap_total} application entry points detected; showing "
772
+ f"{len(bootstrap)} (deployment-annotated entries first)."
773
+ )
774
+ elif _bootstrap_total > 1:
775
+ result["bootstrap_note"] = (
776
+ f"{_bootstrap_total} application entry points detected."
777
+ )
745
778
  if security:
746
779
  result["security"] = security
747
780
  if controllers:
sourcecode/summarizer.py CHANGED
@@ -362,11 +362,16 @@ class ProjectSummarizer:
362
362
 
363
363
  parts = [lead]
364
364
 
365
- # Stack with frameworks — keep brief, skip internal module listings
365
+ # Stack with frameworks — keep brief, skip internal module listings.
366
+ # Read the primary flag set by the single authority (classifier.select_primary_stack);
367
+ # never re-select here — a second selector would let the summary name a different
368
+ # stack than architecture_summary / stacks[].primary (the polyglot divergence).
366
369
  non_tooling_stacks = self._filter_non_tooling_stacks(sm)
367
370
  primary = None
368
371
  if non_tooling_stacks:
369
- primary = self._select_summary_primary_stack(non_tooling_stacks)
372
+ primary = next(
373
+ (s for s in non_tooling_stacks if s.primary), non_tooling_stacks[0]
374
+ )
370
375
  frameworks = [fw.name for fw in primary.frameworks[:2]]
371
376
  if frameworks:
372
377
  parts.append(f"Stack: {primary.stack.capitalize()} ({', '.join(frameworks)})")
@@ -580,21 +585,6 @@ class ProjectSummarizer:
580
585
  ]
581
586
  return filtered or sm.stacks
582
587
 
583
- def _select_summary_primary_stack(self, stacks: list) -> Any:
584
- def score(stack: Any) -> tuple[int, int, int, int]:
585
- root_manifest_hits = 0
586
- if self.root is not None:
587
- root_manifest_hits = sum(
588
- 1 for manifest in stack.manifests
589
- if (self.root / manifest).is_file()
590
- )
591
- framework_hits = len(stack.frameworks)
592
- primary_hint = 1 if stack.primary else 0
593
- confidence = {"low": 0, "medium": 1, "high": 2}.get(stack.confidence, 0)
594
- return (root_manifest_hits, framework_hits, primary_hint, confidence)
595
-
596
- return max(stacks, key=score)
597
-
598
588
  def _is_tooling_path(self, path: str | None) -> bool:
599
589
  if not path:
600
590
  return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.5.13
3
+ Version: 2.5.16
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
@@ -1,20 +1,20 @@
1
- sourcecode/__init__.py,sha256=M8eYjrmWCXZgccRJ042BhY9QS6VmNX4SDwD_gMX9cvo,309
1
+ sourcecode/__init__.py,sha256=1AEidIhoB0bOc0Hb9b3t-h8cPmjSPMex9CxZXJhKi98,309
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/archetype.py,sha256=d7yoN6Tj4OBj-VgSMPEKg4WF9H8FypnZ5A6AkUh5oIE,34484
4
4
  sourcecode/architecture_analyzer.py,sha256=GFc4ek-s1IHWM7pl-0L32WahZ93AmDgrAMcOuBKA5Dk,61463
5
5
  sourcecode/architecture_summary.py,sha256=BVVRHd952cjRhjHnR6CPrvKgaa-tdM16l-pBi1yDCPs,32395
6
- sourcecode/ast_extractor.py,sha256=FEsYTsMCXbJfSYQZRk1k-I-PjUNGnKqO1IxMftsgA5U,51351
6
+ sourcecode/ast_extractor.py,sha256=gBxHVDdmY5o6lcBxawYYA-2CpGbcJ8EEAewGLi5CfQ4,51477
7
7
  sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
8
8
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
9
9
  sourcecode/caller_metrics.py,sha256=HG8RCOkpzzsEGdnMRob6byrwjoj5__t0TEsBj4yR7ks,2574
10
- sourcecode/canonical_ir.py,sha256=5hLAhhSpXNOdZAvPt-J-rXccJE6M3A6gVhmhU0Tym6Y,29269
10
+ sourcecode/canonical_ir.py,sha256=LdP_Ri3rl0M5p-MY0ypVvQwhq1WuUF1FmaHid7zyzEg,29807
11
11
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
12
- sourcecode/classifier.py,sha256=rkMapdklDuIv3rXrNEwwL65UldkvTn8VuKa73GPoSDo,18849
13
- sourcecode/cli.py,sha256=mlMgyRukRoHXYdAYHBN4z06LszwdRo0QLdhs0KYtup8,323804
12
+ sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
13
+ sourcecode/cli.py,sha256=QH7K8Uw19z5o7eXFPvVcC8ZA1KNUHS0bVL_IdOYC2CQ,324702
14
14
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
15
15
  sourcecode/confidence_analyzer.py,sha256=vnbPI-20FnHdjO6STxHW8fbaxmB4A7y58io63ibFZjc,21586
16
16
  sourcecode/context_cache.py,sha256=maws79rLKDwyZ7c9Q2LwlIxqZHRFlaKzErtO3mglWoA,25036
17
- sourcecode/context_graph.py,sha256=WFtUvfxoE8XtQxtRJLvaF4SJuS00ICjNoTk0sH2_V5U,44454
17
+ sourcecode/context_graph.py,sha256=i2kOX3XiaCqcR20ojbUnqoX-hJbMyLNYc7rl-n15bwE,46426
18
18
  sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
19
19
  sourcecode/context_summarizer.py,sha256=cI2TZMvEhl0BEma12VtPaX6z03ZVBAetVzuK5GaCOvg,6852
20
20
  sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
@@ -24,7 +24,7 @@ sourcecode/dependency_analyzer.py,sha256=fQCaWQ7_BNcVgZv8gweJCwJ1CGlXsz8nw_tqN3a
24
24
  sourcecode/doc_analyzer.py,sha256=05bjTUbDbmnbajD_cgRnACzS8T7xxBKVX4CjkJlhZg8,24411
25
25
  sourcecode/dynamic_argument_surface.py,sha256=iRzrJhs57UXrGvLhNPiTA-e_sdO6ka-wNqc0wIiegiM,2554
26
26
  sourcecode/endpoint_literals.py,sha256=Qf4gTZzvNSFDGDuOvF0YRL9NvaYKKj24klhR5LIOaXc,3263
27
- sourcecode/endpoint_metrics.py,sha256=wkAonXe34ItHlvlGur3YRFUrcZfg6GdiiwiE4bMa21Y,2590
27
+ sourcecode/endpoint_metrics.py,sha256=sLSLUIgiIyvNdOynaxVgHd9SjRF_DbN3QxevaquykaU,2840
28
28
  sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4-W3zWE,5322
29
29
  sourcecode/env_analyzer.py,sha256=aNTyYgQk5noJDfJU6FmasmESOHfiomyJw5EvZqjy6qc,22213
30
30
  sourcecode/error_schema.py,sha256=uwosfNaSujtYm11_732Hu92z5ITV040fQDaIyefSvR4,1683
@@ -45,7 +45,8 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
45
45
  sourcecode/migrate_check.py,sha256=HJhvHuvXDGTJSRQU9_AMx0L94h2TImC1DCGs3m3RTu0,108199
46
46
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
47
47
  sourcecode/output_budget.py,sha256=Js9yUlfQtPhqBl9R6wn_9UHVjjJc3GtLcqyfjf5t50Q,9869
48
- sourcecode/path_filters.py,sha256=809P9hj_QChNLg8wk4HuKcFUZmnDKNz0sKIMbmt_FNE,9138
48
+ sourcecode/path_filters.py,sha256=qPKO7kRmVp2y9zjLNSuAVCbcpZrIInHE4QulLUlzPFI,10412
49
+ sourcecode/perf.py,sha256=GAcEoouPIlPMCQIcHNToxK6K3WdIR-lj9aFg4prOYJI,9743
49
50
  sourcecode/pr_comment_renderer.py,sha256=KmcjMruhR44gjzMDJwjBSkWP9QEvh8xWBLyxzxoRbj0,14542
50
51
  sourcecode/pr_impact.py,sha256=dCDVw83EDbyVf6F9ZmEQmsFz8ruVH7d4mpeKQCIZHM0,16805
51
52
  sourcecode/prepare_context.py,sha256=-Xu33qCETuqw-OFfT3ukvAtxde7krCjQpn7wR3pbdOg,224290
@@ -56,7 +57,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
56
57
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
57
58
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
58
59
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
59
- sourcecode/repository_ir.py,sha256=bNSUKkWQEvFQ2wSZPRdn_9cZfx6TRcPzGtM2EgMm49M,311400
60
+ sourcecode/repository_ir.py,sha256=mLsnKeZkbsEF2Jzl7FOKMtzo4lR9UTyrk2KhbeL4FcE,313566
60
61
  sourcecode/ris.py,sha256=Hw8TakTQ6hku-Abf2k8954NwkrH_sP73_8wVH8x5khc,22079
61
62
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
62
63
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
@@ -67,7 +68,7 @@ sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4Bb
67
68
  sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
68
69
  sourcecode/semantic_integration_engine.py,sha256=rxoCuP-uM_Y4-ELeBN0sz1a88e2eI1XTJDgsS57IOLI,15513
69
70
  sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_pd4,10782
70
- sourcecode/serializer.py,sha256=FmQo7eojA_z3ptLW-Vgze5mrEJ6FabcZqiwbQWWTgFI,130056
71
+ sourcecode/serializer.py,sha256=UCGwxPZw9jS_amhU4om9a4Z6VOGOpttNuPpLuhGnuuU,132052
71
72
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
72
73
  sourcecode/spring_findings.py,sha256=EX7kLZLN74CFyR9iZPm3CI115BfFKNd4WRPuErNljZM,5729
73
74
  sourcecode/spring_impact.py,sha256=MkUI27OCOhkjvBoA4f9X1eDBIY0SmyPuLVCWIfZgOcE,72430
@@ -75,7 +76,7 @@ sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16
75
76
  sourcecode/spring_security_audit.py,sha256=Rk-aSohezdc7YDYbSoJquVnwpkDB8ty1BCD-4Hc4R5A,22832
76
77
  sourcecode/spring_semantic.py,sha256=jteQ1PkY9ArFJv0embg_jBIdbOxqrk9mQ2Xz8OF_FKA,14214
77
78
  sourcecode/spring_tx_analyzer.py,sha256=QMXkxa_hrqDVN3D8WJsd0nT7vfdZCT1E8ZWNoFFbvgU,34692
78
- sourcecode/summarizer.py,sha256=SWj-y5BJ_Qw89ljLg-KXaLsvU87usOw-raTckPutCk4,26976
79
+ sourcecode/summarizer.py,sha256=sr0-tfecFKCr-fSkPPWbl-t9HC7SY2ZxkjnnXX8DB2A,26621
79
80
  sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
80
81
  sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
81
82
  sourcecode/validation_inference.py,sha256=UtmYv-d4TEzT1ihGEipAhtXDrwq0XtVtjMpb3i7NsNc,10815
@@ -91,7 +92,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
91
92
  sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
92
93
  sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
93
94
  sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
94
- sourcecode/detectors/java.py,sha256=ut8czxGzhFUxRAS5LeG78wev8kJolC7LG8IESDJZTNk,42349
95
+ sourcecode/detectors/java.py,sha256=t6uDee_4pTmkbCQ5aXXqtVfoPLnvVBhnxYYVrv5qrdk,44499
95
96
  sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
96
97
  sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
97
98
  sourcecode/detectors/parsers.py,sha256=ug9K31tyHqinmv0HkIVQVjdTZpBv67FYKAEf52YXOSM,3178
@@ -137,8 +138,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
137
138
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
138
139
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
139
140
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
140
- sourcecode-2.5.13.dist-info/METADATA,sha256=hAqH0Dxo8V6o4vDdpqalzX3njpQNvHIO8-t5zglFo68,10852
141
- sourcecode-2.5.13.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
142
- sourcecode-2.5.13.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
143
- sourcecode-2.5.13.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
144
- sourcecode-2.5.13.dist-info/RECORD,,
141
+ sourcecode-2.5.16.dist-info/METADATA,sha256=zog1kb8UlXASniP5jKFyloD-2tLZ-sLMt80zRHaDqo0,10852
142
+ sourcecode-2.5.16.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
143
+ sourcecode-2.5.16.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
144
+ sourcecode-2.5.16.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
145
+ sourcecode-2.5.16.dist-info/RECORD,,