sourcecode 1.58.0__py3-none-any.whl → 1.59.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
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.58.0"
3
+ __version__ = "1.59.0"
@@ -347,12 +347,17 @@ _SPRING_OTHER: frozenset[str] = frozenset({
347
347
  "@MatrixParam", "@CookieParam", "@Context",
348
348
  })
349
349
 
350
- _PUBLISH_EVENT_RE = re.compile(r'\.publishEvent\s*\(\s*new\s+(\w+)\s*[(\{]')
350
+ # Optional generic type args between the event class name and its constructor
351
+ # parens — diamond `<>` or explicit `<Order>` / `<Map<String,Integer>>`.
352
+ # Required so `publishEvent(new SaveServiceEvent<>(obj))` (generic event wrappers,
353
+ # e.g. OpenMRS *ServiceEvent family) is recognised as a publisher edge.
354
+ _GENERIC_ARGS = r'(?:<[^<>;{}()]*(?:<[^<>;{}()]*>[^<>;{}()]*)*>)?'
355
+ _PUBLISH_EVENT_RE = re.compile(r'\.publishEvent\s*\(\s*new\s+(\w+)\s*' + _GENERIC_ARGS + r'\s*[(\{]')
351
356
 
352
357
  # Two-step publish: SomeEvent var = new SomeEvent(...); publisher.publishEvent(var)
353
358
  # Used when event is created before passing to publishEvent (common pattern).
354
359
  _PUBLISH_EVENT_CALL_RE = re.compile(r'\.publishEvent\s*\(')
355
- _NEW_EVENT_INSTANTIATION_RE = re.compile(r'\bnew\s+(\w+Event)\s*[\({]')
360
+ _NEW_EVENT_INSTANTIATION_RE = re.compile(r'\bnew\s+(\w+Event)\s*' + _GENERIC_ARGS + r'\s*[\({]')
356
361
 
357
362
  # Keycloak SPI event fire pattern: XxxEvent.fire(session, ...)
358
363
  _FIRE_EVENT_RE = re.compile(r'\b(\w+Event)\.fire\s*\(')
@@ -1122,6 +1127,102 @@ def _build_same_package_map(symbols: list[SymbolRecord]) -> dict[str, dict[str,
1122
1127
  return result
1123
1128
 
1124
1129
 
1130
+ # Reserved words that read like calls (`if (`, `for (`, …). Can never be sibling
1131
+ # method names (Java reserves them), but guard the intra-class scan anyway.
1132
+ _CALL_KEYWORDS: frozenset[str] = frozenset({
1133
+ "if", "for", "while", "switch", "catch", "return", "synchronized",
1134
+ "new", "super", "this", "assert",
1135
+ })
1136
+
1137
+
1138
+ def _method_body(raw_lines: list[str], start_idx: int) -> str:
1139
+ """Source text of the method/constructor declared at raw_lines[start_idx],
1140
+ from its first '{' to the matching '}' (brace-matched, string/char aware).
1141
+
1142
+ The signature prefix before '{' is dropped so the method's own name is not
1143
+ scanned as a call site. Returns "" for a bodyless declaration (abstract /
1144
+ interface method terminated by ';' before any '{').
1145
+ """
1146
+ depth = 0
1147
+ started = False
1148
+ out: list[str] = []
1149
+ for i in range(start_idx, len(raw_lines)):
1150
+ line = raw_lines[i]
1151
+ if not started:
1152
+ if "{" in line:
1153
+ started = True
1154
+ line = line[line.index("{"):]
1155
+ elif ";" in line:
1156
+ return "" # bodyless declaration
1157
+ else:
1158
+ continue # multi-line signature continuation
1159
+ out.append(line)
1160
+ depth += _count_net_braces(line)
1161
+ if depth <= 0:
1162
+ break
1163
+ return "\n".join(out)
1164
+
1165
+
1166
+ def _intra_class_call_edges(symbols: list[SymbolRecord], source: str) -> list[RelationEdge]:
1167
+ """Method-level `calls` edges for intra-class invocations.
1168
+
1169
+ `discontinueOrder(){ stopOrder(...); }` →
1170
+ OrderServiceImpl#discontinueOrder --calls--> OrderServiceImpl#stopOrder.
1171
+
1172
+ Class-level call scans miss method-to-method calls within a single class, so
1173
+ impact-chain on a private/helper method (e.g. OrderServiceImpl#stopOrder) found
1174
+ zero method-level callers and degraded to a wrong class-level expansion. Resolves
1175
+ bare `m(...)` and `this.m(...)` calls whose target is a sibling METHOD of the same
1176
+ class. Overloads link to all same-name siblings (arity not resolved by regex).
1177
+ Deterministic, in-source only — no cross-file or runtime inference.
1178
+ """
1179
+ callers = [s for s in symbols if s.symbol_kind in ("method", "constructor") and s.line]
1180
+ if not callers:
1181
+ return []
1182
+
1183
+ # Per-class sibling index: simple method name → [method FQNs]. Constructors are
1184
+ # not call targets (a `new X(...)` site is a different relation).
1185
+ siblings: dict[str, dict[str, list[str]]] = {}
1186
+ for s in symbols:
1187
+ if s.symbol_kind == "method" and "#" in s.symbol:
1188
+ cls = _enclosing_class(s.symbol)
1189
+ name = s.symbol.rsplit("#", 1)[1]
1190
+ siblings.setdefault(cls, {}).setdefault(name, []).append(s.symbol)
1191
+ if not siblings:
1192
+ return []
1193
+
1194
+ raw_lines = source.splitlines()
1195
+ edges: list[RelationEdge] = []
1196
+ for caller in callers:
1197
+ cls = _enclosing_class(caller.symbol)
1198
+ sib = siblings.get(cls)
1199
+ if not sib:
1200
+ continue
1201
+ body = _method_body(raw_lines, caller.line - 1)
1202
+ if not body:
1203
+ continue
1204
+ body = _STRING_LITERAL_RE.sub('', _strip_java_comments(body))
1205
+ for name, fqns in sib.items():
1206
+ if name in _CALL_KEYWORDS:
1207
+ continue
1208
+ # bare `name(` (not preceded by word char or '.') OR explicit `this.name(`
1209
+ pat = (r'(?<![\w.])' + re.escape(name) + r'\s*\('
1210
+ + r'|\bthis\s*\.\s*' + re.escape(name) + r'\s*\(')
1211
+ if not re.search(pat, body):
1212
+ continue
1213
+ for tgt in fqns:
1214
+ if tgt == caller.symbol:
1215
+ continue # skip self-recursion self-loop
1216
+ edges.append(RelationEdge(
1217
+ from_symbol=caller.symbol,
1218
+ to_symbol=tgt,
1219
+ type="calls",
1220
+ confidence="medium",
1221
+ evidence={"type": "method_call", "value": f"{name}(...)"},
1222
+ ))
1223
+ return edges
1224
+
1225
+
1125
1226
  def _build_relations(
1126
1227
  symbols: list[SymbolRecord],
1127
1228
  raw_imports: list[str],
@@ -1557,6 +1658,9 @@ def _build_relations(
1557
1658
  evidence={"type": "method_call", "value": f"{_tgt.split('.')[-1]}.…(…)"},
1558
1659
  ))
1559
1660
 
1661
+ # ── Intra-class method calls: EnclosingMethod -> calls -> SameClass#sibling ──
1662
+ edges.extend(_intra_class_call_edges(symbols, source))
1663
+
1560
1664
  seen: set[tuple[str, str, str]] = set()
1561
1665
  unique: list[RelationEdge] = []
1562
1666
  for e in edges:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.58.0
3
+ Version: 1.59.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
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  **Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
42
42
 
43
- ![Version](https://img.shields.io/badge/version-1.58.0-blue)
43
+ ![Version](https://img.shields.io/badge/version-1.59.0-blue)
44
44
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
45
45
 
46
46
  ---
@@ -114,7 +114,7 @@ pipx install sourcecode
114
114
 
115
115
  ```bash
116
116
  sourcecode version
117
- # sourcecode 1.58.0
117
+ # sourcecode 1.59.0
118
118
  ```
119
119
 
120
120
  ---
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=zPgb29z23K85U3nCKBwXfaqA7j0bV7r7tMzxrVDP-9w,103
1
+ sourcecode/__init__.py,sha256=2epIg2XeTy7k3HoxrmwEPHso7xkeVSRJ3jh1LHHjjkU,103
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=liCwQmLgb5vplohy8arjYxs_HOIv5C9MjLh_gY6bc5Q,44115
4
4
  sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
@@ -45,7 +45,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
45
45
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
46
46
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
47
47
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
48
- sourcecode/repository_ir.py,sha256=n1H0OROkD1dHvpWAtDoYNHGlTkVhQpYIFqIQ3jf3mgs,214101
48
+ sourcecode/repository_ir.py,sha256=YjpmR-Tdfnep1ryjfCQLjttLoKOcq0_11XbhuomDFX8,218657
49
49
  sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
50
50
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
51
51
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
@@ -102,8 +102,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
102
102
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
103
103
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
104
104
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
105
- sourcecode-1.58.0.dist-info/METADATA,sha256=hmIYx5s3UECwA6NNlKD6HEetY1pl-6t76_KF0YKZysA,38831
106
- sourcecode-1.58.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
107
- sourcecode-1.58.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
108
- sourcecode-1.58.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
109
- sourcecode-1.58.0.dist-info/RECORD,,
105
+ sourcecode-1.59.0.dist-info/METADATA,sha256=Tl_qHFWT-yXKwDto4VaZ_H9xnvBvabtL_oCosI4fn-0,38831
106
+ sourcecode-1.59.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
107
+ sourcecode-1.59.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
108
+ sourcecode-1.59.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
109
+ sourcecode-1.59.0.dist-info/RECORD,,