sourcecode 1.41.0__py3-none-any.whl → 1.44.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.41.0"
3
+ __version__ = "1.44.0"
@@ -131,9 +131,22 @@ _CLASS_DECL_RE = re.compile(
131
131
  r'\s*\{',
132
132
  )
133
133
 
134
+ # CH-004c: detect a type declaration that participates structurally via inheritance
135
+ # (`class X extends Base` / `interface I extends A` / `class C implements I`). Used by
136
+ # the fast pre-scan to NOT skip annotation-free files that still own extends/implements
137
+ # edges the impact graph needs. `[^{;]*?` keeps the match within a single declaration
138
+ # head (stops at the body `{` or a `;`), matching the precision of _CLASS_DECL_RE.
139
+ _INHERIT_PRESCAN_RE = re.compile(
140
+ r'\b(?:class|interface)\s+[A-Z]\w*[^{;]*?\b(?:extends|implements)\b'
141
+ )
142
+
134
143
  _METHOD_DECL_RE = re.compile(
135
144
  r'^(?P<modifiers>(?:(?:public|private|protected|static|final|synchronized'
136
145
  r'|abstract|default|native|strictfp|override)\s+)*)'
146
+ # Inline (modifier-position) annotations, e.g. `public @ResponseBody Vets foo()`
147
+ # or `@Valid`, `@Nonnull`. Without this the whole declaration fails to match and
148
+ # the method (its endpoint + return-type edge) is silently dropped (CH-003/Fase 22).
149
+ r'(?P<inline_anns>(?:@[\w.]+(?:\s*\([^)]*\))?\s+)*)'
137
150
  r'(?:<[\w,\s?]+>\s+)?'
138
151
  r'(?P<return_type>(?:void|boolean|byte|char|short|int|long|float|double|String|[\w.<>\[\]?,]+)\s+)'
139
152
  r'(?P<name>[a-z_]\w*)\s*\(',
@@ -830,6 +843,11 @@ def _extract_symbols(
830
843
  if mname not in _JAVA_KEYWORDS:
831
844
  fqn = f"{class_fqn}#{mname}"
832
845
  modifiers = _parse_modifier_str(mth_m.group("modifiers") or "")
846
+ # Fold modifier-position inline annotations into pending_anns so
847
+ # symbol_kind / endpoint detection see them (e.g. @ResponseBody).
848
+ for _inl in re.findall(r'@[\w.]+', mth_m.group("inline_anns") or ""):
849
+ if _inl not in pending_anns:
850
+ pending_anns.append(_inl)
833
851
  used = _resolve_types_from_text(stripped, import_map)
834
852
  conf = "high" if ("public" in modifiers or pending_anns) else "medium"
835
853
 
@@ -1256,7 +1274,11 @@ def _build_relations(
1256
1274
  # commas so each base produces its own edge and the reverse graph sees
1257
1275
  # every supertype (not a single mangled token).
1258
1276
  for base in _split_supertype_list(extends_str):
1259
- to = import_map.get(base, base)
1277
+ # CH-004: resolve same-package / wildcard-imported supertypes to their
1278
+ # FQN (not just import_map), else a same-package `extends Base` stays a
1279
+ # bare name and the implementation_graph cannot link sub→supertype.
1280
+ _sbase = re.sub(r'<.*', '', base).strip()
1281
+ to = _resolve_dep_type(_sbase) or import_map.get(_sbase, base)
1260
1282
  edges.append(RelationEdge(
1261
1283
  from_symbol=class_fqn,
1262
1284
  to_symbol=to,
@@ -1267,7 +1289,8 @@ def _build_relations(
1267
1289
 
1268
1290
  if implements_str:
1269
1291
  for base in _split_supertype_list(implements_str):
1270
- to = import_map.get(base, base)
1292
+ _sbase = re.sub(r'<.*', '', base).strip()
1293
+ to = _resolve_dep_type(_sbase) or import_map.get(_sbase, base)
1271
1294
  edges.append(RelationEdge(
1272
1295
  from_symbol=class_fqn,
1273
1296
  to_symbol=to,
@@ -1330,6 +1353,32 @@ def _build_relations(
1330
1353
  evidence={"type": "annotation", "value": ann},
1331
1354
  ))
1332
1355
 
1356
+ # ── Type-usage: return-type edges (CH-003 / Fase 22) ──────────────────────
1357
+ # A method's declared return type is a real dependency edge the call/DI graph
1358
+ # misses: for a value/DTO/response type returned (esp. @ResponseBody) by a
1359
+ # controller handler, this is the ONLY link from the type back to its endpoint.
1360
+ # method → returnTypeFQN, type="returns". Resolution reuses _resolve_dep_type,
1361
+ # so only in-repo / imported types produce edges (primitives & void are skipped).
1362
+ _PRIMITIVE_RETURNS: frozenset[str] = frozenset({
1363
+ "void", "boolean", "byte", "char", "short", "int", "long", "float",
1364
+ "double", "String", "Object",
1365
+ })
1366
+ for sym in symbols:
1367
+ if sym.type != "method" or not sym.return_type:
1368
+ continue
1369
+ _ret_base = re.sub(r'<.*', '', sym.return_type).replace("[]", "").strip()
1370
+ if not _ret_base or _ret_base in _PRIMITIVE_RETURNS or not _ret_base[0].isupper():
1371
+ continue
1372
+ _ret_fqn = _resolve_dep_type(_ret_base)
1373
+ if _ret_fqn and _ret_fqn != _enclosing_class(sym.symbol):
1374
+ edges.append(RelationEdge(
1375
+ from_symbol=sym.symbol,
1376
+ to_symbol=_ret_fqn,
1377
+ type="returns",
1378
+ confidence="high",
1379
+ evidence={"type": "return_type", "value": sym.return_type},
1380
+ ))
1381
+
1333
1382
  _class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
1334
1383
 
1335
1384
  # Strip comments before event scanning to prevent Javadoc examples from
@@ -1425,6 +1474,46 @@ def _build_relations(
1425
1474
  evidence={"type": "signature", "value": f"{ev_label}<{event_simple}>"},
1426
1475
  ))
1427
1476
 
1477
+ # ── Type-usage: instantiation edges (CH-003 / Fase 22) ────────────────────
1478
+ # `new T(...)` couples the instantiating class to T — a type-usage edge the
1479
+ # call/DI graph misses. Attributed at class level (one primary type per file,
1480
+ # mirroring the publishes_event scan). Controllers are EXCLUDED: their value-type
1481
+ # coupling is already covered precisely by `returns` edges, and a class-level
1482
+ # edge from a controller would broaden a DTO's impact to every route on that
1483
+ # controller (false breadth). Method-level attribution is a future refinement.
1484
+ # Controller-like = class-level @RestController/@Controller OR a class that owns
1485
+ # any endpoint-handler method (mappings are often only method-level).
1486
+ _classes_with_endpoints = {
1487
+ _enclosing_class(s.symbol) for s in symbols if s.symbol_kind == "endpoint"
1488
+ }
1489
+ _instantiating_controllers = {
1490
+ s.symbol for s in symbols
1491
+ if s.type in ("class", "interface")
1492
+ and (
1493
+ any(a in ("@RestController", "@Controller") for a in s.annotations)
1494
+ or s.symbol in _classes_with_endpoints
1495
+ )
1496
+ }
1497
+ _non_controller_classes = [
1498
+ s for s in _class_syms if s.symbol not in _instantiating_controllers
1499
+ ]
1500
+ if _non_controller_classes:
1501
+ _inst_targets: set[str] = set()
1502
+ for _m in re.finditer(r'\bnew\s+([A-Z]\w*)\s*[(<]', _source_no_comments):
1503
+ _t_fqn = _resolve_dep_type(_m.group(1))
1504
+ if _t_fqn:
1505
+ _inst_targets.add(_t_fqn)
1506
+ for cls_sym in _non_controller_classes:
1507
+ for _tgt in sorted(_inst_targets):
1508
+ if _tgt != cls_sym.symbol:
1509
+ edges.append(RelationEdge(
1510
+ from_symbol=cls_sym.symbol,
1511
+ to_symbol=_tgt,
1512
+ type="instantiates",
1513
+ confidence="medium",
1514
+ evidence={"type": "method_call", "value": f"new {_tgt.split('.')[-1]}(...)"},
1515
+ ))
1516
+
1428
1517
  seen: set[tuple[str, str, str]] = set()
1429
1518
  unique: list[RelationEdge] = []
1430
1519
  for e in edges:
@@ -3052,6 +3141,14 @@ def build_repo_ir(
3052
3141
  '@RequiredArgsConstructor', '@AllArgsConstructor',
3053
3142
  '@Inject', '@ApplicationScoped', '@RequestScoped', '@Singleton',
3054
3143
  '@EnableMethodSecurity', '@EnableGlobalMethodSecurity',
3144
+ # Field/setter injection markers (CH-004). A class wired purely by field
3145
+ # injection — no class-level stereotype — is still a node in the DI graph:
3146
+ # e.g. an abstract base controller that holds @Autowired/@Resource services
3147
+ # its concrete subclasses inherit. Omitting these pre-scan-skips such classes,
3148
+ # dropping their injects edges, so impact-chain cannot reach callers through
3149
+ # them (Broadleaf: AbstractCheckoutController → checkout endpoints went missing).
3150
+ '@Autowired', '@Resource', '@Qualifier', '@Value',
3151
+ '@PersistenceContext', '@PersistenceUnit',
3055
3152
  # JPA / persistence (needed for stereotype detection in all commands)
3056
3153
  '@Entity', '@MappedSuperclass', '@Embeddable',
3057
3154
  # AOP / messaging / event sourcing
@@ -3098,7 +3195,8 @@ def build_repo_ir(
3098
3195
  _meta_chars_read += len(source)
3099
3196
  # Fast pre-scan: if file has no relevant annotations skip full extraction.
3100
3197
  # Still register package/class name for same-package resolution.
3101
- if not any(marker in source for marker in _effective_markers):
3198
+ if not any(marker in source for marker in _effective_markers) \
3199
+ and not _INHERIT_PRESCAN_RE.search(source):
3102
3200
  pkg_m = _PKG_RE.search(source)
3103
3201
  _pkg = pkg_m.group(1) if pkg_m else ""
3104
3202
  # Minimal class-name symbols for same-package map (no methods/fields)
@@ -129,6 +129,50 @@ class ImpactChainResult:
129
129
  return d
130
130
 
131
131
 
132
+ # ---------------------------------------------------------------------------
133
+ # CH-003 — value/DTO type blind-spot detection
134
+ # ---------------------------------------------------------------------------
135
+ # The impact graph models call + DI/injection edges but not *type-usage* edges
136
+ # (constructor instantiation `new T()`, field/local-variable type, and method
137
+ # return type incl. @ResponseBody). For a service/repository the call+DI edges
138
+ # cover the real blast radius; for a value/DTO/response type they cover nothing,
139
+ # so its impact is invisible — and an all-zero result reported at confidence=high
140
+ # reads as "globally dead" (a dangerous false negative). Until type-usage edges
141
+ # are modelled (Fase 22 / CH-002), positively identify plain value types and
142
+ # downgrade confidence + warn instead of asserting an empty high-confidence result.
143
+ _STEREOTYPE_ANNOTATIONS = frozenset({
144
+ "@Service", "@Repository", "@Controller", "@RestController",
145
+ "@Component", "@Configuration", "@ControllerAdvice",
146
+ "@RestControllerAdvice", "@Bean",
147
+ })
148
+ _VALUE_TYPE_KINDS = frozenset({"class", "enum", "record"})
149
+
150
+
151
+ def _is_unmodeled_value_type(cir, class_fqn: str, model) -> bool:
152
+ """True iff class_fqn is positively a plain value/DTO type whose blast radius
153
+ flows only through type-usage edges the impact graph does not model.
154
+
155
+ Conservative: returns False whenever the type cannot be positively confirmed
156
+ (node metadata absent, stereotype annotation present, recognized Spring role,
157
+ or controller) so spine symbols and incomplete-IR cases keep legacy behaviour.
158
+ """
159
+ graph = (getattr(cir, "_raw_ir", None) or {}).get("graph") or {}
160
+ node = next((n for n in (graph.get("nodes") or []) if n.get("fqn") == class_fqn), None)
161
+ if node is None:
162
+ return False # cannot confirm — stay conservative (preserves IC-V3)
163
+ if (node.get("symbol_kind") or node.get("type")) not in _VALUE_TYPE_KINDS:
164
+ return False # interface / annotation / bean-method etc.
165
+ anns = node.get("annotations") or []
166
+ if any(a.split("(", 1)[0] in _STEREOTYPE_ANNOTATIONS for a in anns):
167
+ return False # Spring stereotype bean — spine participant
168
+ if (node.get("role") or "other") != "other":
169
+ return False # recognized Spring role (repository/service/controller/mapper)
170
+ controllers = getattr(getattr(model, "endpoint_index", None), "controller_fqns", frozenset())
171
+ if class_fqn in controllers:
172
+ return False
173
+ return True
174
+
175
+
132
176
  # ---------------------------------------------------------------------------
133
177
  # Symbol resolution
134
178
  # ---------------------------------------------------------------------------
@@ -706,9 +750,31 @@ class ImpactOrchestrator:
706
750
  impact_findings_raw,
707
751
  )
708
752
 
753
+ # CH-003: empty blast radius on a positively-identified value/DTO type is a
754
+ # type-usage blind spot, not proof of dead code — warn + drop confidence.
755
+ empty_blast = (
756
+ not direct_callers and not indirect_callers
757
+ and not endpoints_affected and not subtype_classes_added
758
+ )
759
+ value_type_blind_spot = (
760
+ empty_blast
761
+ and "#" not in resolved_symbol
762
+ and resolution != "not_found"
763
+ and _is_unmodeled_value_type(cir, resolved_symbol, model)
764
+ )
765
+ if value_type_blind_spot:
766
+ warnings.append(
767
+ "Type-usage edges not modeled (CH-003): this type's blast radius flows "
768
+ "through instantiation (new T()), field/local types, and method return "
769
+ "types (incl. @ResponseBody) — edges impact-chain does not yet track. "
770
+ "An empty result is NOT proof the type is unused."
771
+ )
772
+
709
773
  confidence: str
710
774
  if resolution == "not_found":
711
775
  confidence = "low"
776
+ elif value_type_blind_spot:
777
+ confidence = "low"
712
778
  elif resolution == "partial" or warnings:
713
779
  confidence = "medium"
714
780
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.41.0
3
+ Version: 1.44.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.39.0-blue)
43
+ ![Version](https://img.shields.io/badge/version-1.44.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.39.0
117
+ # sourcecode 1.44.0
118
118
  ```
119
119
 
120
120
  ---
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=O5H1yHp5Huzjec-tRroSc8Z3rDJeqdAGgZ9MFZAqxnY,103
1
+ sourcecode/__init__.py,sha256=KCoWiYA4njgsXUEDMnmPU0kheOgqm-RpLky5FPkilQw,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
@@ -44,7 +44,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
44
44
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
45
45
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
46
46
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
47
- sourcecode/repository_ir.py,sha256=-rHQSinIMEa6lcbPdyoJzYB-USB9yX_9bsaWJgJqwHs,202432
47
+ sourcecode/repository_ir.py,sha256=WjDYwbBm-eWp-k6aSdBrgO_XcRGuP-Llp0TZHBhq8bY,208237
48
48
  sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
49
49
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
50
50
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
@@ -54,7 +54,7 @@ sourcecode/semantic_analyzer.py,sha256=4OdG6tTSnTvq3_dSWMbQu8Ad1ndSCKeG-b9qM4hIx
54
54
  sourcecode/serializer.py,sha256=TGzftrSKitZrtl6Hh-R05s4KdTOxwTmph_lGDbo2Wzg,125015
55
55
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
56
56
  sourcecode/spring_findings.py,sha256=G7Or2lKBUQbcTDqudLvSs9XvNg_YoAa-_lBOG_ULs8E,5457
57
- sourcecode/spring_impact.py,sha256=pg_SWogs2ylNF-Tjz_6AgPtJGePrr3VQeyeFYK6-NzA,35228
57
+ sourcecode/spring_impact.py,sha256=rUKSiCfXh9NpC9a97KvjCu6Kn8bYezTnMDY3F7sgtCI,38737
58
58
  sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
59
59
  sourcecode/spring_security_audit.py,sha256=XtPJ1SXlZJ8k6VYmaWuAp7Bbir4UmreAL7doIGQ5I7o,20595
60
60
  sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM,13307
@@ -101,8 +101,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
101
101
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
102
102
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
103
103
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
104
- sourcecode-1.41.0.dist-info/METADATA,sha256=fbTRvu0l7OOyoWnIFFNkEmSfEPtFzg3uSNvD33MsbLU,32359
105
- sourcecode-1.41.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
106
- sourcecode-1.41.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
107
- sourcecode-1.41.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
108
- sourcecode-1.41.0.dist-info/RECORD,,
104
+ sourcecode-1.44.0.dist-info/METADATA,sha256=0xHtFeZJ0-CMPHbgVACahR0Z11bN0WooJP34TW2xA4g,32359
105
+ sourcecode-1.44.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
106
+ sourcecode-1.44.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
107
+ sourcecode-1.44.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
108
+ sourcecode-1.44.0.dist-info/RECORD,,