sourcecode 1.4.0__py3-none-any.whl → 1.5.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.4.0"
3
+ __version__ = "1.5.0"
@@ -182,8 +182,19 @@ class ArchitectureAnalyzer:
182
182
  ddd_result = self._detect_ddd(sm.file_paths)
183
183
  if ddd_result is not None:
184
184
  ddd_pattern, ddd_layers, ddd_contexts, ddd_layer_names = ddd_result
185
- domains_for_ddd = self._cluster_domains(filtered) if len(filtered) >= 2 else []
186
185
  module_files = self._build_ddd_module_files(sm.file_paths, ddd_contexts)
186
+ # Use DDD bounded context names as domains so --architecture shows each
187
+ # context as a distinct domain instead of collapsing all files under
188
+ # the Maven path segment (e.g. "java").
189
+ domains_for_ddd = [
190
+ ArchitectureDomain(
191
+ name=n,
192
+ files=module_files.get(n, []),
193
+ role="DDD bounded context",
194
+ confidence="high",
195
+ )
196
+ for n in ddd_contexts
197
+ ]
187
198
  bc_list = [
188
199
  BoundedContext(name=n, modules=module_files.get(n, []), confidence="high")
189
200
  for n in ddd_contexts
@@ -1172,6 +1172,51 @@ def _detect_role(path: str, contract: FileContract) -> str:
1172
1172
  return "util"
1173
1173
 
1174
1174
 
1175
+ # ---------------------------------------------------------------------------
1176
+ # MyBatis XML mapper extractor
1177
+ # ---------------------------------------------------------------------------
1178
+
1179
+ def _extract_mybatis_xml(rel_path: str, source: str) -> FileContract:
1180
+ """Extract namespace and SQL operations from a MyBatis *Mapper.xml file."""
1181
+ import re as _re
1182
+ from xml.etree import ElementTree
1183
+
1184
+ _NS_STRIP = _re.compile(r"\{[^}]+\}")
1185
+ _SQL_OPS = frozenset({"select", "insert", "update", "delete"})
1186
+
1187
+ exports: list[ExportRecord] = []
1188
+ namespace: str | None = None
1189
+
1190
+ try:
1191
+ root_elem = ElementTree.fromstring(source.encode("utf-8"))
1192
+ namespace = root_elem.get("namespace") or None
1193
+ for elem in root_elem:
1194
+ tag = _NS_STRIP.sub("", elem.tag).lower()
1195
+ if tag in _SQL_OPS:
1196
+ op_id = (elem.get("id") or "").strip()
1197
+ if op_id:
1198
+ # type_ref carries select/insert/update/delete for the serializer
1199
+ exports.append(ExportRecord(kind="query", name=op_id, type_ref=tag))
1200
+ except Exception:
1201
+ return FileContract(
1202
+ path=rel_path,
1203
+ language="mybatis-xml",
1204
+ role="mybatis-mapper",
1205
+ extraction_method="heuristic",
1206
+ limitations=["xml_parse_error: failed to parse mapper XML"],
1207
+ )
1208
+
1209
+ deps = [f"namespace:{namespace}"] if namespace else []
1210
+ return FileContract(
1211
+ path=rel_path,
1212
+ language="mybatis-xml",
1213
+ role="mybatis-mapper",
1214
+ exports=exports,
1215
+ dependencies=deps,
1216
+ extraction_method="heuristic",
1217
+ )
1218
+
1219
+
1175
1220
  # ---------------------------------------------------------------------------
1176
1221
  # AstExtractor public class
1177
1222
  # ---------------------------------------------------------------------------
@@ -1191,6 +1236,16 @@ class AstExtractor:
1191
1236
  return self._ts_ok
1192
1237
 
1193
1238
  def extract(self, path: Path, root: Optional[Path] = None) -> Optional[FileContract]:
1239
+ # MyBatis mapper XML — handled before the language map lookup so .xml
1240
+ # files are only processed when they match the mapper naming convention.
1241
+ if path.suffix.lower() == ".xml" and path.name.endswith("Mapper.xml"):
1242
+ try:
1243
+ source = path.read_text(encoding="utf-8", errors="replace")
1244
+ except OSError:
1245
+ return None
1246
+ rel_path = str(path.relative_to(root)).replace("\\", "/") if root else path.name
1247
+ return _extract_mybatis_xml(rel_path, source)
1248
+
1194
1249
  ext = path.suffix.lower()
1195
1250
  language = _LANGUAGE_MAP.get(ext)
1196
1251
  if language is None:
@@ -219,9 +219,18 @@ class ContractPipeline:
219
219
  fname = Path(pn).name
220
220
  return any(fname.startswith(pat) or f".{pat.strip('.')}" in fname for pat in _TEST_PATTERNS)
221
221
 
222
+ def _is_extractable(p: str) -> bool:
223
+ suf = Path(p).suffix.lower()
224
+ if suf in _SRC_EXTENSIONS:
225
+ return True
226
+ # MyBatis mapper XML files — only *Mapper.xml, not all XML
227
+ if suf == ".xml" and p.endswith("Mapper.xml"):
228
+ return True
229
+ return False
230
+
222
231
  src_paths = [
223
232
  p for p in file_paths
224
- if Path(p).suffix.lower() in _SRC_EXTENSIONS
233
+ if _is_extractable(p)
225
234
  and not scorer.is_noise(p)
226
235
  and (symbol is not None or changed_only or not _is_test(p))
227
236
  ]
@@ -1191,6 +1191,18 @@ class DependencyAnalyzer:
1191
1191
  limitations: list[str] = []
1192
1192
  if not records:
1193
1193
  limitations.append("java: pom.xml sin dependencias parseables (puede usar BOM o propiedades)")
1194
+
1195
+ # Warn when Spring Boot BOM manages transitive deps — they can't be resolved statically.
1196
+ parent_artifact_local = (
1197
+ root_elem.findtext(f"{ns}parent/{ns}artifactId") or ""
1198
+ ).strip() if parent_elem is not None else ""
1199
+ if parent_artifact_local == "spring-boot-starter-parent" and parent_version:
1200
+ limitations.append(
1201
+ f"spring_boot_bom_detected: transitive deps managed by Spring Boot BOM "
1202
+ f"v{parent_version}, not resolved statically. "
1203
+ "Run 'mvn dependency:tree' for the full transitive tree."
1204
+ )
1205
+
1194
1206
  return records, limitations
1195
1207
 
1196
1208
  def _analyze_gradle(self, root: Path) -> tuple[list[DependencyRecord], list[str]]:
@@ -18,7 +18,7 @@ _NS_TAG_RE = re.compile(r"\{[^}]+\}")
18
18
 
19
19
  _MAX_FILE_SIZE = 256 * 1024 # 256 KB
20
20
  _MAX_JAVA_ENTRY_SCAN = 1000
21
- _MAX_ANNOTATION_ENTRY_POINTS = 500
21
+ _MAX_ANNOTATION_ENTRY_POINTS = 1000
22
22
 
23
23
  _REST_CONTROLLER_RE = re.compile(r'@RestController\b')
24
24
  _MVC_CONTROLLER_RE = re.compile(r'@Controller\b')
@@ -236,10 +236,12 @@ class JavaDetector(AbstractDetector):
236
236
  ]
237
237
 
238
238
  # 2. Annotation-based scan: @RestController, @WebFilter, FilterRegistrationBean
239
- scan_candidates = [
240
- p for p in all_java
241
- if "/test/" not in p and "/tests/" not in p
242
- ][:_MAX_JAVA_ENTRY_SCAN]
239
+ # Prioritize Controller-named files so all REST controllers are detected
240
+ # even in large codebases where total files > _MAX_JAVA_ENTRY_SCAN.
241
+ _non_test = [p for p in all_java if "/test/" not in p and "/tests/" not in p]
242
+ _ctrl_files = [p for p in _non_test if "Controller" in p]
243
+ _other_files = [p for p in _non_test if "Controller" not in p]
244
+ scan_candidates = _ctrl_files + _other_files[:max(0, _MAX_JAVA_ENTRY_SCAN - len(_ctrl_files))]
243
245
 
244
246
  annotation_eps: list[EntryPoint] = []
245
247
  for rel_path in scan_candidates:
sourcecode/serializer.py CHANGED
@@ -1439,10 +1439,31 @@ def _serialize_contract_java(c: Any) -> dict[str, Any]:
1439
1439
  return item
1440
1440
 
1441
1441
 
1442
+ def _serialize_contract_mybatis_xml(c: Any) -> dict[str, Any]:
1443
+ """Serialize a MyBatis *Mapper.xml contract."""
1444
+ item: dict[str, Any] = {"path": c.path, "language": "mybatis-xml"}
1445
+ # Extract namespace stored as "namespace:<fqn>" in dependencies
1446
+ for dep in (c.dependencies or []):
1447
+ if dep.startswith("namespace:"):
1448
+ item["namespace"] = dep[len("namespace:"):]
1449
+ break
1450
+ exports_out: list[dict] = []
1451
+ for e in c.exports:
1452
+ entry: dict = {"kind": e.kind, "name": e.name}
1453
+ if getattr(e, "type_ref", None):
1454
+ entry["type"] = e.type_ref
1455
+ exports_out.append(entry)
1456
+ if exports_out:
1457
+ item["exports"] = exports_out
1458
+ return item
1459
+
1460
+
1442
1461
  def _serialize_contract_minimal(c: Any) -> dict[str, Any]:
1443
1462
  """Serialize one FileContract to minimal format."""
1444
1463
  if getattr(c, "language", None) == "java":
1445
1464
  return _serialize_contract_java(c)
1465
+ if getattr(c, "language", None) == "mybatis-xml":
1466
+ return _serialize_contract_mybatis_xml(c)
1446
1467
  item: dict[str, Any] = {"path": c.path, "role": c.role}
1447
1468
 
1448
1469
  if c.is_changed:
@@ -1560,6 +1581,11 @@ def _contract_view_standard(
1560
1581
  if contracts:
1561
1582
  serialized: list[dict[str, Any]] = []
1562
1583
  for c in contracts:
1584
+ if getattr(c, "language", None) == "mybatis-xml":
1585
+ item = _serialize_contract_mybatis_xml(c)
1586
+ item["relevance_score"] = round(c.relevance_score, 3)
1587
+ serialized.append(item)
1588
+ continue
1563
1589
  item: dict[str, Any] = {
1564
1590
  "path": c.path,
1565
1591
  "language": c.language,
@@ -1609,7 +1635,7 @@ def _contract_view_standard(
1609
1635
  item["ranking_reasons"] = non_trivial
1610
1636
  item["method"] = c.extraction_method
1611
1637
  serialized.append(item)
1612
- result["file_contracts"] = serialized
1638
+ result["contracts"] = serialized
1613
1639
 
1614
1640
  # Optional analysis sections (deep mode or when analyzers ran)
1615
1641
  if include_optional:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -1,8 +1,8 @@
1
- sourcecode/__init__.py,sha256=7vIUDLA50NqbZpGWjbWgBBlC-d6NEDyVgAmqGi5Qy1I,102
1
+ sourcecode/__init__.py,sha256=81eXvIk2uxc6dFJg9ND6pJ-xePSGbA0ZUJgr1h_bZ48,102
2
2
  sourcecode/adaptive_scanner.py,sha256=6dh34C2qZXyRbw-8xBhbEwDdXanM6CRFRWayVoYITnA,10190
3
- sourcecode/architecture_analyzer.py,sha256=qzDW3_lQv__czQ-qs6AqEEoMvTfhfp7M7kNslPuQy7A,32128
3
+ sourcecode/architecture_analyzer.py,sha256=oPmGPf9_p6y8Z7SIGHEu57nVYwxycIovDzBvlHl-l5k,32557
4
4
  sourcecode/architecture_summary.py,sha256=J9yoLgh8wXwIRrT6q6JooB6PekivbOEYpJz4BUXdalk,20545
5
- sourcecode/ast_extractor.py,sha256=L_MbLz22TdF_P21-nSSxSG9hZcMdtUjRKmPTWFx5NmU,47732
5
+ sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
6
6
  sourcecode/classifier.py,sha256=GKTMN8qKZX7ponSwDJfN08RrasI4CVpq1_gFBgEopps,7093
7
7
  sourcecode/cli.py,sha256=_oYoP4_AAFGw0gg-FBGn_DKh7y49xmwuL6D_Bafl2w4,74497
8
8
  sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
@@ -10,9 +10,9 @@ sourcecode/confidence_analyzer.py,sha256=HcaewB2pZaZ_hfKrZWtr_yPMY2-CxS1zzTUD7c4
10
10
  sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
11
11
  sourcecode/context_summarizer.py,sha256=CiQrfBEzun949bWvmLabWoj2HhPn6Lw62ofqnsy0FlQ,6503
12
12
  sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
13
- sourcecode/contract_pipeline.py,sha256=mjI1MgFebZW6uQAei0aheW7VPmZntH5eVXiyqkE2N9w,26332
13
+ sourcecode/contract_pipeline.py,sha256=_mZn0GD7UjjJ7E79DmmYjF0sVkuOLBZEgZHqgXVyTrs,26648
14
14
  sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
15
- sourcecode/dependency_analyzer.py,sha256=LGEcpbpPl7cO4-TjPO02XAJRwiAhE1ad0QQDIjAqmEA,55595
15
+ sourcecode/dependency_analyzer.py,sha256=p4ljXhkcGBbFlhaZuPrsjOVjDXaKLTg0Gor2p4qFPP0,56208
16
16
  sourcecode/doc_analyzer.py,sha256=a1CIClCNmfYM3ku4bdgwHQpmb6Js4wdJZ1V5EYLo04I,24345
17
17
  sourcecode/entrypoint_classifier.py,sha256=gvKgl0f5T8ol1r4JMmkeqGHuZTfZJiOwFOWdc7EYwYw,4061
18
18
  sourcecode/env_analyzer.py,sha256=GxCidahAAIptTdDFIlVB6URd4HBnBlIX_SqUov3MBRQ,22076
@@ -29,7 +29,7 @@ sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBG
29
29
  sourcecode/scanner.py,sha256=aM3h9-DCQ3xKpeHpHYdo2vX6T5P95HA_YwZbkAVNwmo,8288
30
30
  sourcecode/schema.py,sha256=rNxpDUgOfvJDzz6DKitL-5_0UAh0YomNwTsSSe8PafY,24066
31
31
  sourcecode/semantic_analyzer.py,sha256=12TwXYkYbDcBdu0heX_EmfPM2EkO8a_r5osf0SaeQbs,88956
32
- sourcecode/serializer.py,sha256=qqqH84ensw31P6ElqivPUwmYtb6AR7KTENM2nFJSde4,68403
32
+ sourcecode/serializer.py,sha256=2ztWD4FCebPD0D5tY-kNgLlsATaQsPrSQY6rLQeemRI,69467
33
33
  sourcecode/summarizer.py,sha256=ZuzIdm3t8A-d5MuQL0TSNLrd-L0IQIuguIxeNXMNJf8,16070
34
34
  sourcecode/tree_utils.py,sha256=Fj9OIuUksBvgibNd3feog0sMDjVypJzPexp5lvMoYWI,1424
35
35
  sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
@@ -42,7 +42,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
42
42
  sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
43
43
  sourcecode/detectors/heuristic.py,sha256=bCqqgbHavl4Sse3dqT8mwmo1wAdgeJr7VyXOmfClLKo,3387
44
44
  sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
45
- sourcecode/detectors/java.py,sha256=acRCelw-SdxaPsJsBJEGybFTqe1esPpgqKibB9xbJoI,16277
45
+ sourcecode/detectors/java.py,sha256=0NKsy1uls5wvQSK7DJP_fM-v5_uuMEr_an47caelLHE,16612
46
46
  sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
47
47
  sourcecode/detectors/nodejs.py,sha256=7fsyAmrGkkguX6U80HUQpIe9MRaYyi_A7zbaRtmFmGc,13097
48
48
  sourcecode/detectors/parsers.py,sha256=ugPg8yNUf0Ai1gA7Fnn6wAkYGFjTxRodSP3IeViYJJ4,2290
@@ -60,8 +60,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
60
60
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
61
61
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
62
62
  sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
63
- sourcecode-1.4.0.dist-info/METADATA,sha256=vVglc6X8WawZuI4yHs6aWszUHNLtVowyFpCRRSA4INM,20411
64
- sourcecode-1.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
- sourcecode-1.4.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
66
- sourcecode-1.4.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
67
- sourcecode-1.4.0.dist-info/RECORD,,
63
+ sourcecode-1.5.0.dist-info/METADATA,sha256=kdZtCFuIhrWj6KDM4nZ-tMqWzuwiY2hXt4C8hP6PETc,20411
64
+ sourcecode-1.5.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
+ sourcecode-1.5.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
66
+ sourcecode-1.5.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
67
+ sourcecode-1.5.0.dist-info/RECORD,,