sourcecode 1.36.3__py3-none-any.whl → 1.36.5__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.36.3"
3
+ __version__ = "1.36.5"
@@ -486,6 +486,31 @@ def _normalize_type_name(raw: str) -> str:
486
486
  return raw.strip()
487
487
 
488
488
 
489
+ def _split_supertype_list(raw: str) -> list[str]:
490
+ """Split an ``extends``/``implements`` clause into individual base type names.
491
+
492
+ Handles multiple supertypes (interfaces may extend several) and strips generic
493
+ type arguments *before* splitting so that commas inside ``<...>`` do not corrupt
494
+ the result. e.g. ``"VetRepository, Repository<Vet, Integer>"`` → ``["VetRepository",
495
+ "Repository"]``.
496
+ """
497
+ if not raw or not raw.strip():
498
+ return []
499
+ # Iteratively remove (possibly nested) generic parameters so any commas they
500
+ # contain are gone before we split on the top-level commas.
501
+ prev = None
502
+ stripped = raw
503
+ while prev != stripped:
504
+ prev = stripped
505
+ stripped = re.sub(r'<[^<>]*>', '', stripped)
506
+ bases: list[str] = []
507
+ for piece in stripped.split(","):
508
+ base = re.sub(r'<.*', '', piece).strip()
509
+ if base:
510
+ bases.append(base)
511
+ return bases
512
+
513
+
489
514
  def _parse_param_types(params_str: str) -> list[str]:
490
515
  """Parse "(Long id, @Valid String name)" → ["Long", "String"].
491
516
 
@@ -1166,29 +1191,29 @@ def _build_relations(
1166
1191
  class_fqn = f"{package}.{name}" if package else name
1167
1192
 
1168
1193
  if extends_str:
1169
- base = re.sub(r'<.*', '', extends_str).strip()
1170
- to = import_map.get(base, base)
1171
- edges.append(RelationEdge(
1172
- from_symbol=class_fqn,
1173
- to_symbol=to,
1174
- type="extends",
1175
- confidence="high",
1176
- evidence={"type": "signature", "value": f"extends {extends_str}"},
1177
- ))
1194
+ # An interface may extend multiple interfaces (e.g.
1195
+ # `extends VetRepository, Repository<Vet, Integer>`); split on top-level
1196
+ # commas so each base produces its own edge and the reverse graph sees
1197
+ # every supertype (not a single mangled token).
1198
+ for base in _split_supertype_list(extends_str):
1199
+ to = import_map.get(base, base)
1200
+ edges.append(RelationEdge(
1201
+ from_symbol=class_fqn,
1202
+ to_symbol=to,
1203
+ type="extends",
1204
+ confidence="high",
1205
+ evidence={"type": "signature", "value": f"extends {extends_str}"},
1206
+ ))
1178
1207
 
1179
1208
  if implements_str:
1180
- for iface in implements_str.split(","):
1181
- iface = iface.strip()
1182
- base = re.sub(r'<.*', '', iface).strip()
1183
- if not base:
1184
- continue
1209
+ for base in _split_supertype_list(implements_str):
1185
1210
  to = import_map.get(base, base)
1186
1211
  edges.append(RelationEdge(
1187
1212
  from_symbol=class_fqn,
1188
1213
  to_symbol=to,
1189
1214
  type="implements",
1190
1215
  confidence="high",
1191
- evidence={"type": "signature", "value": f"implements {iface}"},
1216
+ evidence={"type": "signature", "value": f"implements {base}"},
1192
1217
  ))
1193
1218
 
1194
1219
  # mapped_to edges: controller class → class-level @RequestMapping path prefix.
@@ -2973,6 +2998,11 @@ def build_repo_ir(
2973
2998
  '@Aspect', '@Aggregate', '@Document',
2974
2999
  # Spring Data
2975
3000
  '@Query', '@NamedQuery',
3001
+ # Profile-gated beans/interfaces (e.g. Spring Data repository specializations
3002
+ # like `@Profile("spring-data-jpa") interface FooRepo extends FooRepository`).
3003
+ # Without this marker such interfaces are pre-scan-skipped and their
3004
+ # extends/implements edges are lost — making them invisible to impact analysis.
3005
+ '@Profile',
2976
3006
  )
2977
3007
  # Pre-pass: collect custom meta-annotation names from @interface definitions
2978
3008
  # that compose known Spring stereotypes (e.g. @DomainService = @Service + @Transactional).
@@ -3517,6 +3547,40 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
3517
3547
  "note": "interface-based Spring MVC controller — URL mapped via XML",
3518
3548
  })
3519
3549
 
3550
+ # Detect controllers whose HTTP mappings live on an IMPLEMENTED interface that is
3551
+ # not part of the scanned source surface. The dominant case is openapi-generator
3552
+ # "interface-only" output (e.g. PetV2Api, VetsApi) emitted under
3553
+ # target/generated-sources, which the scanner excludes. Such a controller carries
3554
+ # @RestController/@Controller and an `implements XxxApi` clause but contributes no
3555
+ # method-level routes, so its endpoints are invisible. Emit an explicit warning so
3556
+ # an empty/partial surface is not silently misread as "no endpoints / no security".
3557
+ _CONTROLLER_ANNS = {"@RestController", "@Controller"}
3558
+ _IMPLEMENTS_RE = _re.compile(r'\bimplements\s+(.+)$')
3559
+ _routed_fqns = {route.get("effective_class") for route in routes}
3560
+ interface_defined_controllers: list[str] = []
3561
+ endpoint_warnings: list[str] = []
3562
+ for sym in all_symbols:
3563
+ if sym.type != "class":
3564
+ continue
3565
+ if not (_CONTROLLER_ANNS & set(sym.annotations)):
3566
+ continue
3567
+ if sym.symbol in _routed_fqns:
3568
+ continue # already contributes routes — surface is captured
3569
+ m = _IMPLEMENTS_RE.search(sym.signature or "")
3570
+ if not m:
3571
+ continue
3572
+ ifaces = _split_supertype_list(m.group(1))
3573
+ api_ifaces = [i for i in ifaces if i.endswith("Api")]
3574
+ if not api_ifaces:
3575
+ continue
3576
+ interface_defined_controllers.append(sym.symbol)
3577
+ endpoint_warnings.append(
3578
+ f"{sym.symbol.split('.')[-1]} implements {', '.join(api_ifaces)}: HTTP "
3579
+ "mappings are declared on the implemented interface (commonly generated by "
3580
+ "openapi-generator under target/generated-sources, which is not scanned). "
3581
+ "Endpoint surface for this controller is NOT captured."
3582
+ )
3583
+
3520
3584
  endpoints: list[dict] = []
3521
3585
  for route in routes:
3522
3586
  handler = (
@@ -3643,7 +3707,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
3643
3707
  if e.get("security", {}).get("policy") == "none_detected"
3644
3708
  )
3645
3709
 
3646
- return {
3710
+ result: dict[str, Any] = {
3647
3711
  "endpoints": endpoints,
3648
3712
  "total": len(endpoints),
3649
3713
  "no_security_signal": no_security_signal,
@@ -3651,6 +3715,12 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
3651
3715
  # Keep legacy field name for backward compat, now means same as no_security_signal
3652
3716
  "undocumented": no_security_signal,
3653
3717
  }
3718
+ # Surface incomplete-endpoint warnings (interface-defined controllers) only when
3719
+ # present, to keep output backward-compatible for the common case.
3720
+ if endpoint_warnings:
3721
+ result["warnings"] = endpoint_warnings
3722
+ result["interface_defined_controllers"] = interface_defined_controllers
3723
+ return result
3654
3724
 
3655
3725
 
3656
3726
  def find_java_files(root: Path, *, max_files: int = 8000, limitations: list[str] | None = None) -> list[str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.36.3
3
+ Version: 1.36.5
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,8 +40,8 @@ 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.36.1-blue)
44
- ![Python](https://img.shields.io/badge/python-3.10%2B-green)
43
+ ![Version](https://img.shields.io/badge/version-1.36.5-blue)
44
+ ![Python](https://img.shields.io/badge/python-3.9%2B-green)
45
45
 
46
46
  ---
47
47
 
@@ -114,7 +114,7 @@ pipx install sourcecode
114
114
 
115
115
  ```bash
116
116
  sourcecode version
117
- # sourcecode 1.36.1
117
+ # sourcecode 1.36.5
118
118
  ```
119
119
 
120
120
  ---
@@ -283,7 +283,7 @@ Specifically:
283
283
  - Architecture pattern detection best for Spring MVC layered apps; SPI/plugin architectures (e.g. Quarkus extension model) may be misclassified
284
284
  - Endpoint recall for JAX-RS subresource locator pattern is ~65%
285
285
  - `impact` on implementation classes (e.g. `OrderServiceImpl`) returns 0 callers in Spring Boot — callers inject the interface via `@Autowired`. Always target the interface. When `direct_callers: []` with `confidence_level: high` for a `@Service` class, re-query the interface.
286
- - `no_security_signal` on endpoints means no method-level annotations found — does **not** mean the endpoint is unsecured. Projects using Spring Security filter chains show 100% `no_security_signal` even when fully secured.
286
+ - `no_security_signal` on endpoints means no recognized method-level annotation found — does **not** mean the endpoint is unsecured. Projects using Spring Security filter chains show 100% `no_security_signal` even when fully secured. Projects using a custom authorization annotation can teach the scanner via [`sourcecode.config.json`](#sourcecodeconfigjson-repo-root).
287
287
  - `spring-audit` and `impact-chain` are **Java/Spring only** — non-Java repos return `spring_detected: false`
288
288
  - Event topology via `--type events` does not resolve Kafka/RabbitMQ/Redis message routes — only Spring ApplicationEvent and `@EventListener` chains
289
289
  - Self-invocation TX bypass (calling `@Transactional` method from the same class without going through the proxy) is not detected
@@ -411,6 +411,8 @@ Detects structural Spring anomalies that survive code review and tests, but caus
411
411
 
412
412
  Returns structured findings with `severity`, `confidence`, `symbol`, `source_file`, `evidence`, `explanation`, and `fix_hint`. JAVA/SPRING ONLY.
413
413
 
414
+ Endpoints guarded by a project-specific authorization annotation are treated as secured (not flagged `SEC-001`) once declared in [`sourcecode.config.json`](#sourcecodeconfigjson-repo-root).
415
+
414
416
  ### `impact-chain` — systemic blast radius with TX/SEC enrichment [free]
415
417
 
416
418
  ```bash
@@ -716,3 +718,29 @@ Or: `export SOURCECODE_TELEMETRY=0`
716
718
  ```bash
717
719
  sourcecode config # show version, config file path, telemetry status
718
720
  ```
721
+
722
+ ### `sourcecode.config.json` (repo root)
723
+
724
+ Optional, per-repo. Loaded from the root of the repo being analyzed. Absent or
725
+ malformed config is ignored — the tool behaves exactly as without it.
726
+
727
+ **Custom security annotations.** Teach `endpoints`, `spring-audit`, and `explain`
728
+ about project-specific authorization annotations (otherwise reported as
729
+ `policy: "none_detected"`):
730
+
731
+ ```json
732
+ {
733
+ "customSecurityAnnotations": [
734
+ {
735
+ "fullyQualifiedName": "com.example.security.M3FiltroSeguridad",
736
+ "shortName": "M3FiltroSeguridad",
737
+ "resourceParam": "nombreRecurso",
738
+ "levelParam": "nivelRequerido"
739
+ }
740
+ ]
741
+ }
742
+ ```
743
+
744
+ `resourceParam` / `levelParam` are optional and name the annotation attributes to
745
+ surface as `resourceName` / `requiredLevel`. Matching endpoints report
746
+ `policy: "custom"` and drop out of the `no_security_signal` count.
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=R2EBl2n5HISPyZl0TZ5WWj834XZILTkphQ_J6rBP3zA,103
1
+ sourcecode/__init__.py,sha256=o9c3MUAMMI3cMbNxDQQBYbcvFPS_juLXH25a0Wy8jC0,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
@@ -42,7 +42,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
42
42
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
43
43
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
44
44
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
45
- sourcecode/repository_ir.py,sha256=XtkzRTl3Ze3G6D2sXtXxlyxjkjSL2BuiKb8GQehkbdE,188320
45
+ sourcecode/repository_ir.py,sha256=JuB0Dl1OMbQN-bd8smuVIIKwesLJJga5rCQyUSRm5xA,191971
46
46
  sourcecode/ris.py,sha256=RcqLVwC-doFcKKViYDkCjZLBqf_wzLES7-F6vHEeWzE,20419
47
47
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
48
48
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
@@ -98,8 +98,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
98
98
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
99
99
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
100
100
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
101
- sourcecode-1.36.3.dist-info/METADATA,sha256=QGvZo4o1EjzCa6zRqOWBZQs7y9RjI1AEsVBrVc4BNvw,31056
102
- sourcecode-1.36.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
103
- sourcecode-1.36.3.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
104
- sourcecode-1.36.3.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
105
- sourcecode-1.36.3.dist-info/RECORD,,
101
+ sourcecode-1.36.5.dist-info/METADATA,sha256=ogbSzTG2t7MK6U6P2szIf9nfK99AVOFb9OLXoAm2KMw,32243
102
+ sourcecode-1.36.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
103
+ sourcecode-1.36.5.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
104
+ sourcecode-1.36.5.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
105
+ sourcecode-1.36.5.dist-info/RECORD,,