sourcecode 1.41.0__py3-none-any.whl → 1.42.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.42.0"
@@ -134,6 +134,10 @@ _CLASS_DECL_RE = re.compile(
134
134
  _METHOD_DECL_RE = re.compile(
135
135
  r'^(?P<modifiers>(?:(?:public|private|protected|static|final|synchronized'
136
136
  r'|abstract|default|native|strictfp|override)\s+)*)'
137
+ # Inline (modifier-position) annotations, e.g. `public @ResponseBody Vets foo()`
138
+ # or `@Valid`, `@Nonnull`. Without this the whole declaration fails to match and
139
+ # the method (its endpoint + return-type edge) is silently dropped (CH-003/Fase 22).
140
+ r'(?P<inline_anns>(?:@[\w.]+(?:\s*\([^)]*\))?\s+)*)'
137
141
  r'(?:<[\w,\s?]+>\s+)?'
138
142
  r'(?P<return_type>(?:void|boolean|byte|char|short|int|long|float|double|String|[\w.<>\[\]?,]+)\s+)'
139
143
  r'(?P<name>[a-z_]\w*)\s*\(',
@@ -830,6 +834,11 @@ def _extract_symbols(
830
834
  if mname not in _JAVA_KEYWORDS:
831
835
  fqn = f"{class_fqn}#{mname}"
832
836
  modifiers = _parse_modifier_str(mth_m.group("modifiers") or "")
837
+ # Fold modifier-position inline annotations into pending_anns so
838
+ # symbol_kind / endpoint detection see them (e.g. @ResponseBody).
839
+ for _inl in re.findall(r'@[\w.]+', mth_m.group("inline_anns") or ""):
840
+ if _inl not in pending_anns:
841
+ pending_anns.append(_inl)
833
842
  used = _resolve_types_from_text(stripped, import_map)
834
843
  conf = "high" if ("public" in modifiers or pending_anns) else "medium"
835
844
 
@@ -1330,6 +1339,32 @@ def _build_relations(
1330
1339
  evidence={"type": "annotation", "value": ann},
1331
1340
  ))
1332
1341
 
1342
+ # ── Type-usage: return-type edges (CH-003 / Fase 22) ──────────────────────
1343
+ # A method's declared return type is a real dependency edge the call/DI graph
1344
+ # misses: for a value/DTO/response type returned (esp. @ResponseBody) by a
1345
+ # controller handler, this is the ONLY link from the type back to its endpoint.
1346
+ # method → returnTypeFQN, type="returns". Resolution reuses _resolve_dep_type,
1347
+ # so only in-repo / imported types produce edges (primitives & void are skipped).
1348
+ _PRIMITIVE_RETURNS: frozenset[str] = frozenset({
1349
+ "void", "boolean", "byte", "char", "short", "int", "long", "float",
1350
+ "double", "String", "Object",
1351
+ })
1352
+ for sym in symbols:
1353
+ if sym.type != "method" or not sym.return_type:
1354
+ continue
1355
+ _ret_base = re.sub(r'<.*', '', sym.return_type).replace("[]", "").strip()
1356
+ if not _ret_base or _ret_base in _PRIMITIVE_RETURNS or not _ret_base[0].isupper():
1357
+ continue
1358
+ _ret_fqn = _resolve_dep_type(_ret_base)
1359
+ if _ret_fqn and _ret_fqn != _enclosing_class(sym.symbol):
1360
+ edges.append(RelationEdge(
1361
+ from_symbol=sym.symbol,
1362
+ to_symbol=_ret_fqn,
1363
+ type="returns",
1364
+ confidence="high",
1365
+ evidence={"type": "return_type", "value": sym.return_type},
1366
+ ))
1367
+
1333
1368
  _class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
1334
1369
 
1335
1370
  # Strip comments before event scanning to prevent Javadoc examples from
@@ -1425,6 +1460,46 @@ def _build_relations(
1425
1460
  evidence={"type": "signature", "value": f"{ev_label}<{event_simple}>"},
1426
1461
  ))
1427
1462
 
1463
+ # ── Type-usage: instantiation edges (CH-003 / Fase 22) ────────────────────
1464
+ # `new T(...)` couples the instantiating class to T — a type-usage edge the
1465
+ # call/DI graph misses. Attributed at class level (one primary type per file,
1466
+ # mirroring the publishes_event scan). Controllers are EXCLUDED: their value-type
1467
+ # coupling is already covered precisely by `returns` edges, and a class-level
1468
+ # edge from a controller would broaden a DTO's impact to every route on that
1469
+ # controller (false breadth). Method-level attribution is a future refinement.
1470
+ # Controller-like = class-level @RestController/@Controller OR a class that owns
1471
+ # any endpoint-handler method (mappings are often only method-level).
1472
+ _classes_with_endpoints = {
1473
+ _enclosing_class(s.symbol) for s in symbols if s.symbol_kind == "endpoint"
1474
+ }
1475
+ _instantiating_controllers = {
1476
+ s.symbol for s in symbols
1477
+ if s.type in ("class", "interface")
1478
+ and (
1479
+ any(a in ("@RestController", "@Controller") for a in s.annotations)
1480
+ or s.symbol in _classes_with_endpoints
1481
+ )
1482
+ }
1483
+ _non_controller_classes = [
1484
+ s for s in _class_syms if s.symbol not in _instantiating_controllers
1485
+ ]
1486
+ if _non_controller_classes:
1487
+ _inst_targets: set[str] = set()
1488
+ for _m in re.finditer(r'\bnew\s+([A-Z]\w*)\s*[(<]', _source_no_comments):
1489
+ _t_fqn = _resolve_dep_type(_m.group(1))
1490
+ if _t_fqn:
1491
+ _inst_targets.add(_t_fqn)
1492
+ for cls_sym in _non_controller_classes:
1493
+ for _tgt in sorted(_inst_targets):
1494
+ if _tgt != cls_sym.symbol:
1495
+ edges.append(RelationEdge(
1496
+ from_symbol=cls_sym.symbol,
1497
+ to_symbol=_tgt,
1498
+ type="instantiates",
1499
+ confidence="medium",
1500
+ evidence={"type": "method_call", "value": f"new {_tgt.split('.')[-1]}(...)"},
1501
+ ))
1502
+
1428
1503
  seen: set[tuple[str, str, str]] = set()
1429
1504
  unique: list[RelationEdge] = []
1430
1505
  for e in edges:
@@ -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.42.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
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=O5H1yHp5Huzjec-tRroSc8Z3rDJeqdAGgZ9MFZAqxnY,103
1
+ sourcecode/__init__.py,sha256=qXpArucbDluvdwLNZfPMDizlqCc5pgoRka_QbdanHCg,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=iSxk1Z5QZehHOG05naDxtT1_vhyyyPHlqzNDoK0HXXg,206573
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.42.0.dist-info/METADATA,sha256=-O4gIf7ctCGPrfXwVg1AeRL0b8K-w9ShFnpbLl4Nyj8,32359
105
+ sourcecode-1.42.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
106
+ sourcecode-1.42.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
107
+ sourcecode-1.42.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
108
+ sourcecode-1.42.0.dist-info/RECORD,,