sourcecode 1.31.6__py3-none-any.whl → 1.31.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +55 -1
- sourcecode/repository_ir.py +87 -6
- {sourcecode-1.31.6.dist-info → sourcecode-1.31.7.dist-info}/METADATA +3 -3
- {sourcecode-1.31.6.dist-info → sourcecode-1.31.7.dist-info}/RECORD +8 -8
- {sourcecode-1.31.6.dist-info → sourcecode-1.31.7.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.6.dist-info → sourcecode-1.31.7.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.6.dist-info → sourcecode-1.31.7.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -2495,6 +2495,10 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2495
2495
|
|
|
2496
2496
|
endpoints: list[dict] = []
|
|
2497
2497
|
seen: set[tuple] = set()
|
|
2498
|
+
# Fix 1: inheritance projection — tracks class data so controllers with ONLY
|
|
2499
|
+
# inherited endpoints (no own @RequestMapping methods) are not silently dropped.
|
|
2500
|
+
_class_info: dict[str, dict] = {}
|
|
2501
|
+
_EXTENDS_RE_LOCAL = _re.compile(r'\bextends\s+(\w+)')
|
|
2498
2502
|
|
|
2499
2503
|
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
2500
2504
|
|
|
@@ -2579,14 +2583,18 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2579
2583
|
# Extract class-level base path and locate class body start
|
|
2580
2584
|
lines = content.splitlines()
|
|
2581
2585
|
|
|
2582
|
-
# First pass: find class/interface declaration line index
|
|
2586
|
+
# First pass: find class/interface declaration line index and extends clause.
|
|
2583
2587
|
class_body_start = 0
|
|
2588
|
+
extends_class_name: Optional[str] = None
|
|
2584
2589
|
for i, line in enumerate(lines):
|
|
2585
2590
|
stripped_l = line.strip()
|
|
2586
2591
|
if (not stripped_l.startswith("//") and not stripped_l.startswith("*")
|
|
2587
2592
|
and ("class " in stripped_l or "interface " in stripped_l)
|
|
2588
2593
|
and _CLASS_RE.search(stripped_l)):
|
|
2589
2594
|
class_body_start = i + 1
|
|
2595
|
+
_ext_m = _EXTENDS_RE_LOCAL.search(stripped_l)
|
|
2596
|
+
if _ext_m:
|
|
2597
|
+
extends_class_name = _ext_m.group(1)
|
|
2590
2598
|
break
|
|
2591
2599
|
|
|
2592
2600
|
# Second pass: extract class-level @RequestMapping path (only before class body).
|
|
@@ -2656,6 +2664,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2656
2664
|
pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
|
|
2657
2665
|
pending_filtro: Optional[str] = None
|
|
2658
2666
|
in_block_comment = False
|
|
2667
|
+
file_own_endpoints: list[tuple] = [] # (http_verb, path_suffix, handler, filtro)
|
|
2659
2668
|
|
|
2660
2669
|
for i in range(class_body_start, len(lines)):
|
|
2661
2670
|
line = lines[i]
|
|
@@ -2708,6 +2717,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2708
2717
|
handler = mm.group(1) if mm else ""
|
|
2709
2718
|
if handler and not handler.startswith("class"):
|
|
2710
2719
|
for http_verb, path_suffix in pending_annotations:
|
|
2720
|
+
file_own_endpoints.append((http_verb, path_suffix, handler, pending_filtro))
|
|
2711
2721
|
for _cb in class_bases: # Bug #1B: one endpoint per class prefix
|
|
2712
2722
|
full_path = (_cb + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
|
|
2713
2723
|
if not full_path.startswith("/"):
|
|
@@ -2733,6 +2743,50 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2733
2743
|
pending_annotations = []
|
|
2734
2744
|
pending_filtro = None
|
|
2735
2745
|
|
|
2746
|
+
# Store per-class data for inheritance projection
|
|
2747
|
+
_class_info[class_name] = {
|
|
2748
|
+
"base_paths": class_bases,
|
|
2749
|
+
"extends_class": extends_class_name,
|
|
2750
|
+
"own_endpoints": file_own_endpoints,
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
# Fix 1: Inheritance projection — controllers whose methods are 100% inherited from a
|
|
2754
|
+
# base class emit 0 endpoints above because no method-level annotations exist in their
|
|
2755
|
+
# file. Walk the inheritance chain and project parent suffixes with the child's base path.
|
|
2756
|
+
for _cls, _data in _class_info.items():
|
|
2757
|
+
if _data["own_endpoints"]:
|
|
2758
|
+
continue
|
|
2759
|
+
_bases = _data["base_paths"]
|
|
2760
|
+
if not _bases or _bases == [""]:
|
|
2761
|
+
continue
|
|
2762
|
+
_chain = _data.get("extends_class")
|
|
2763
|
+
_visited: set[str] = {_cls}
|
|
2764
|
+
while _chain and _chain not in _visited:
|
|
2765
|
+
_visited.add(_chain)
|
|
2766
|
+
_parent = _class_info.get(_chain)
|
|
2767
|
+
if not _parent:
|
|
2768
|
+
break
|
|
2769
|
+
if _parent["own_endpoints"]:
|
|
2770
|
+
for _verb, _suffix, _handler, _filtro in _parent["own_endpoints"]:
|
|
2771
|
+
for _cb in _bases:
|
|
2772
|
+
_fp = (_cb + "/" + _suffix).replace("//", "/").rstrip("/") or "/"
|
|
2773
|
+
if not _fp.startswith("/"):
|
|
2774
|
+
_fp = "/" + _fp
|
|
2775
|
+
_key = (_cls, _handler, _verb, _cb)
|
|
2776
|
+
if _key not in seen:
|
|
2777
|
+
seen.add(_key)
|
|
2778
|
+
_entry: dict[str, Any] = {
|
|
2779
|
+
"method": _verb,
|
|
2780
|
+
"path": _fp,
|
|
2781
|
+
"controller": _cls,
|
|
2782
|
+
"handler": _handler,
|
|
2783
|
+
}
|
|
2784
|
+
if _filtro:
|
|
2785
|
+
_entry["required_permission"] = _filtro
|
|
2786
|
+
endpoints.append(_entry)
|
|
2787
|
+
break
|
|
2788
|
+
_chain = _parent.get("extends_class")
|
|
2789
|
+
|
|
2736
2790
|
endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
|
|
2737
2791
|
undocumented = sum(1 for e in endpoints if "required_permission" not in e)
|
|
2738
2792
|
|
sourcecode/repository_ir.py
CHANGED
|
@@ -178,8 +178,11 @@ _SPRING_OTHER: frozenset[str] = frozenset({
|
|
|
178
178
|
"@PutMapping", "@DeleteMapping", "@PatchMapping", "@Autowired",
|
|
179
179
|
"@Inject", "@Value", "@Qualifier", "@EnableWebSecurity",
|
|
180
180
|
"@SpringBootApplication", "@EnableAutoConfiguration",
|
|
181
|
+
"@EventListener", "@Async", "@Scheduled", "@Cacheable", "@CacheEvict",
|
|
181
182
|
})
|
|
182
183
|
|
|
184
|
+
_PUBLISH_EVENT_RE = re.compile(r'\.publishEvent\s*\(\s*new\s+(\w+)\s*[(\{]')
|
|
185
|
+
|
|
183
186
|
_HTTP_METHOD_MAP: dict[str, str] = {
|
|
184
187
|
"@GetMapping": "GET",
|
|
185
188
|
"@PostMapping": "POST",
|
|
@@ -787,6 +790,33 @@ def _build_relations(
|
|
|
787
790
|
evidence={"type": "structural", "value": f"member of {enclosing}"},
|
|
788
791
|
))
|
|
789
792
|
|
|
793
|
+
# Fix 5: Event flow edges — publishEvent publishers and @EventListener subscribers.
|
|
794
|
+
# listens_to_event: method with @EventListener → resolved event parameter type(s).
|
|
795
|
+
for sym in symbols:
|
|
796
|
+
if sym.type == "method" and "@EventListener" in sym.annotations:
|
|
797
|
+
for imp_fqn in sym.imports_used:
|
|
798
|
+
edges.append(RelationEdge(
|
|
799
|
+
from_symbol=sym.symbol,
|
|
800
|
+
to_symbol=imp_fqn,
|
|
801
|
+
type="listens_to_event",
|
|
802
|
+
confidence="high",
|
|
803
|
+
evidence={"type": "annotation", "value": "@EventListener"},
|
|
804
|
+
))
|
|
805
|
+
|
|
806
|
+
# publishes_event: class that calls publishEvent(new XxxEvent(...)) → event type FQN.
|
|
807
|
+
_class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
|
|
808
|
+
for m in _PUBLISH_EVENT_RE.finditer(source):
|
|
809
|
+
event_simple = m.group(1)
|
|
810
|
+
event_fqn = import_map.get(event_simple, event_simple)
|
|
811
|
+
for cls_sym in _class_syms:
|
|
812
|
+
edges.append(RelationEdge(
|
|
813
|
+
from_symbol=cls_sym.symbol,
|
|
814
|
+
to_symbol=event_fqn,
|
|
815
|
+
type="publishes_event",
|
|
816
|
+
confidence="medium",
|
|
817
|
+
evidence={"type": "method_call", "value": f"publishEvent(new {event_simple})"},
|
|
818
|
+
))
|
|
819
|
+
|
|
790
820
|
seen: set[tuple[str, str, str]] = set()
|
|
791
821
|
unique: list[RelationEdge] = []
|
|
792
822
|
for e in edges:
|
|
@@ -1190,6 +1220,8 @@ _EDGE_REASON_TEMPLATES: dict[str, str] = {
|
|
|
1190
1220
|
"contained_in": "{from_sym} is a member of {to_sym}",
|
|
1191
1221
|
"annotated_with": "{from_sym} is annotated with {to_sym}",
|
|
1192
1222
|
"mapped_to": "Route {to_sym} depends on {from_sym}",
|
|
1223
|
+
"publishes_event": "{from_sym} publishes event {to_sym}",
|
|
1224
|
+
"listens_to_event": "{from_sym} listens for event {to_sym}",
|
|
1193
1225
|
}
|
|
1194
1226
|
|
|
1195
1227
|
# Edge types to exclude from reverse impact traversal (too noisy / non-dependency semantics)
|
|
@@ -1526,13 +1558,43 @@ def _assemble(
|
|
|
1526
1558
|
},
|
|
1527
1559
|
"subsystems": subsystems,
|
|
1528
1560
|
"change_set": change_set_out,
|
|
1529
|
-
"route_surface": route_diffs
|
|
1561
|
+
"route_surface": _build_route_surface(sorted_syms, route_diffs),
|
|
1530
1562
|
"audit": {
|
|
1531
1563
|
"dropped_fields": dropped_fields,
|
|
1532
1564
|
},
|
|
1533
1565
|
}
|
|
1534
1566
|
|
|
1535
1567
|
|
|
1568
|
+
# ---------------------------------------------------------------------------
|
|
1569
|
+
# Route surface helper (Fix 4)
|
|
1570
|
+
# ---------------------------------------------------------------------------
|
|
1571
|
+
|
|
1572
|
+
def _build_route_surface(symbols: list[SymbolRecord], route_diffs: Optional[list[dict]]) -> list[dict]:
|
|
1573
|
+
"""Return route surface: diffs when --since provided, else static snapshot of all endpoints."""
|
|
1574
|
+
if route_diffs:
|
|
1575
|
+
return route_diffs
|
|
1576
|
+
routes: list[dict] = []
|
|
1577
|
+
for sym in symbols:
|
|
1578
|
+
if sym.symbol_kind != "endpoint":
|
|
1579
|
+
continue
|
|
1580
|
+
ann_name = next((a for a in sym.annotations if a in _ENDPOINT_ANNOTATIONS), None)
|
|
1581
|
+
if not ann_name:
|
|
1582
|
+
continue
|
|
1583
|
+
args = sym.annotation_values.get(ann_name, "")
|
|
1584
|
+
path = _parse_route_path(args)
|
|
1585
|
+
method = _parse_route_http_method(ann_name, args)
|
|
1586
|
+
if not path and not method:
|
|
1587
|
+
continue
|
|
1588
|
+
routes.append({
|
|
1589
|
+
"symbol": sym.symbol,
|
|
1590
|
+
"controller": _enclosing_class(sym.symbol),
|
|
1591
|
+
"path": path,
|
|
1592
|
+
"method": method or "GET",
|
|
1593
|
+
"stable_id": sym.stable_id,
|
|
1594
|
+
})
|
|
1595
|
+
return sorted(routes, key=lambda r: (r["controller"], r["path"]))
|
|
1596
|
+
|
|
1597
|
+
|
|
1536
1598
|
# ---------------------------------------------------------------------------
|
|
1537
1599
|
# Public API
|
|
1538
1600
|
# ---------------------------------------------------------------------------
|
|
@@ -1675,7 +1737,17 @@ def apply_ir_size_limits(
|
|
|
1675
1737
|
"remove --summary-only to restore full graph"
|
|
1676
1738
|
),
|
|
1677
1739
|
}
|
|
1678
|
-
|
|
1740
|
+
# Fix 3: keep bounded reverse graph instead of wiping it.
|
|
1741
|
+
full_rg: dict = ir.get("reverse_graph") or {}
|
|
1742
|
+
if full_rg:
|
|
1743
|
+
_rg_sorted = sorted(
|
|
1744
|
+
full_rg.items(),
|
|
1745
|
+
key=lambda x: sum(len(v) for v in x[1].values()),
|
|
1746
|
+
reverse=True,
|
|
1747
|
+
)
|
|
1748
|
+
out["reverse_graph"] = dict(_rg_sorted[:50])
|
|
1749
|
+
else:
|
|
1750
|
+
out["reverse_graph"] = {}
|
|
1679
1751
|
out["impact"] = {
|
|
1680
1752
|
"global_score": (ir.get("impact") or {}).get("global_score", 0),
|
|
1681
1753
|
"ranked_nodes": ranked[:20],
|
|
@@ -1703,10 +1775,19 @@ def apply_ir_size_limits(
|
|
|
1703
1775
|
|
|
1704
1776
|
if kept_fqns is not None or max_edges is not None:
|
|
1705
1777
|
if kept_fqns is not None:
|
|
1706
|
-
#
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1778
|
+
# Fix 2: type-aware priority so semantic edges survive node truncation.
|
|
1779
|
+
# Annotation strings (@Service etc.) and field FQNs are never in kept_fqns,
|
|
1780
|
+
# so "both endpoints kept" drops all injects/annotated_with edges.
|
|
1781
|
+
_SEMANTIC_TYPES = frozenset({"extends", "implements", "injects",
|
|
1782
|
+
"publishes_event", "listens_to_event"})
|
|
1783
|
+
_ANNOTATION_TYPES = frozenset({"annotated_with"})
|
|
1784
|
+
tier1 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _SEMANTIC_TYPES]
|
|
1785
|
+
tier2 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _ANNOTATION_TYPES]
|
|
1786
|
+
tier3 = [e for e in edges
|
|
1787
|
+
if e["from"] in kept_fqns and e["to"] in kept_fqns and e["type"] == "imports"]
|
|
1788
|
+
_seen_e = {(e["from"], e["to"], e["type"]) for e in tier1 + tier2 + tier3}
|
|
1789
|
+
tier4 = [e for e in edges if (e["from"], e["to"], e["type"]) not in _seen_e]
|
|
1790
|
+
edges = tier1 + tier2 + tier3 + tier4
|
|
1710
1791
|
if max_edges is not None:
|
|
1711
1792
|
edges = edges[:max_edges]
|
|
1712
1793
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.7
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -261,7 +261,7 @@ pipx install sourcecode
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
sourcecode version
|
|
264
|
-
# sourcecode 1.31.
|
|
264
|
+
# sourcecode 1.31.7
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=8R-nJsTbK4mRxB0Ix8W0xEdhYCBpK8R-2dyS_nBxiDc,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=MyBa0Hf5HmkudZQDLKrjcWDKETXETXl0mQX1swtTwAA,39091
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
5
5
|
sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
|
|
6
6
|
sourcecode/classifier.py,sha256=-0t0HLc9L9UleMLfclfLM3AXhBjUb_AYyBPDbvgWtac,7755
|
|
7
|
-
sourcecode/cli.py,sha256=
|
|
7
|
+
sourcecode/cli.py,sha256=L0KDRruMogpw9kq7trZ7dmWQuFC746GIEN1oym6LmCE,131802
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
9
9
|
sourcecode/confidence_analyzer.py,sha256=ZUn-Nywi5TEQcuozqK_vfOyPT-a1dYYO42elAtVFV-k,16412
|
|
10
10
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -29,7 +29,7 @@ sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,
|
|
|
29
29
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
30
30
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
31
31
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
32
|
-
sourcecode/repository_ir.py,sha256=
|
|
32
|
+
sourcecode/repository_ir.py,sha256=fLt33wadCMY77jd34YwkYbUT5TyNu2G0LX0x76Krsvo,68348
|
|
33
33
|
sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBGPjQ,14792
|
|
34
34
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
35
35
|
sourcecode/schema.py,sha256=fj3BZ3IcnNV4j21BFIEvz8Qnw_vZoqIbzzRg-qQ-nd0,24530
|
|
@@ -73,8 +73,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
73
73
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
74
74
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
75
75
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
76
|
-
sourcecode-1.31.
|
|
77
|
-
sourcecode-1.31.
|
|
78
|
-
sourcecode-1.31.
|
|
79
|
-
sourcecode-1.31.
|
|
80
|
-
sourcecode-1.31.
|
|
76
|
+
sourcecode-1.31.7.dist-info/METADATA,sha256=O08_eBOxRxeDd8UkUKyIrW6haJHuQvAddgiFAEma0dQ,29083
|
|
77
|
+
sourcecode-1.31.7.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
78
|
+
sourcecode-1.31.7.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
79
|
+
sourcecode-1.31.7.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
80
|
+
sourcecode-1.31.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|