sourcecode 1.1.0__py3-none-any.whl → 1.3.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.1.0"
3
+ __version__ = "1.3.0"
@@ -183,7 +183,11 @@ class ArchitectureAnalyzer:
183
183
  if ddd_result is not None:
184
184
  ddd_pattern, ddd_layers, ddd_contexts, ddd_layer_names = ddd_result
185
185
  domains_for_ddd = self._cluster_domains(filtered) if len(filtered) >= 2 else []
186
- bc_list = [BoundedContext(name=n, confidence="high") for n in ddd_contexts]
186
+ module_files = self._build_ddd_module_files(sm.file_paths, ddd_contexts)
187
+ bc_list = [
188
+ BoundedContext(name=n, modules=module_files.get(n, []), confidence="high")
189
+ for n in ddd_contexts
190
+ ]
187
191
  return ArchitectureAnalysis(
188
192
  requested=True,
189
193
  pattern=ddd_pattern,
@@ -414,6 +418,22 @@ class ArchitectureAnalyzer:
414
418
  ]
415
419
  return "ddd", arch_layers, bounded_context_names, ddd_layer_names
416
420
 
421
+ def _build_ddd_module_files(
422
+ self, paths: list[str], bounded_context_names: list[str]
423
+ ) -> "dict[str, list[str]]":
424
+ """Build a mapping of DDD module name → list of file paths."""
425
+ _DDD_LAYERS = frozenset({"application", "domain", "infrastructure"})
426
+ module_files: dict[str, list[str]] = {}
427
+ for p in paths:
428
+ parts = p.replace("\\", "/").split("/")
429
+ for i, part in enumerate(parts):
430
+ if part in _DDD_LAYERS and i >= 2:
431
+ mod = parts[i - 1]
432
+ if mod in bounded_context_names:
433
+ module_files.setdefault(mod, []).append(p)
434
+ break
435
+ return module_files
436
+
417
437
  def _is_tooling(self, path: str) -> bool:
418
438
  norm = path.replace("\\", "/")
419
439
  return any(norm.startswith(p) for p in _TOOLING_PREFIXES)
@@ -79,6 +79,7 @@ _LANGUAGE_MAP: dict[str, str] = {
79
79
  ".jsx": "jsx",
80
80
  ".mjs": "javascript",
81
81
  ".cjs": "javascript",
82
+ ".java": "java",
82
83
  }
83
84
 
84
85
  _REACT_HOOKS: frozenset[str] = frozenset({
@@ -938,6 +939,92 @@ def _extract_python(path: str, source: str) -> FileContract:
938
939
  )
939
940
 
940
941
 
942
+ # ---------------------------------------------------------------------------
943
+ # Minimal Java extraction (regex-based, no AST)
944
+ # ---------------------------------------------------------------------------
945
+
946
+ _JAVA_CLASS_DECL_RE = re.compile(
947
+ r'public\s+(?:(?:abstract|final|static)\s+)*(class|interface|enum)\s+(\w+)'
948
+ r'(?:\s+extends\s+([\w.]+))?(?:\s+implements\s+([\w.,\s]+?))?(?=\s*[\{<])',
949
+ re.MULTILINE,
950
+ )
951
+ _JAVA_METHOD_SIG_RE = re.compile(
952
+ r'^\s{0,12}public\s+[^\{]+\(',
953
+ re.MULTILINE,
954
+ )
955
+ _JAVA_IMPORT_RE = re.compile(r'^import\s+(?:static\s+)?([^;\s]+)\s*;', re.MULTILINE)
956
+
957
+
958
+ def _extract_java(path: str, source: str) -> FileContract:
959
+ exports: list[ExportRecord] = []
960
+ types: list[TypeDefinition] = []
961
+ functions: list[FunctionSignature] = []
962
+ imports: list[ImportRecord] = []
963
+
964
+ # Class / interface / enum declarations
965
+ for m in _JAVA_CLASS_DECL_RE.finditer(source):
966
+ name = m.group(2)
967
+ extends_str = m.group(3)
968
+ implements_str = m.group(4)
969
+ all_extends: list[str] = []
970
+ if extends_str:
971
+ all_extends.append(extends_str.strip())
972
+ if implements_str:
973
+ all_extends.extend(i.strip() for i in implements_str.split(",") if i.strip())
974
+ types.append(TypeDefinition(name=name, kind="class", fields=[], extends=all_extends))
975
+ exports.append(ExportRecord(name=name, kind="class"))
976
+
977
+ class_names = {t.name for t in types}
978
+
979
+ # Public method signatures (one-line heuristic)
980
+ seen_methods: set[str] = set()
981
+ for m in _JAVA_METHOD_SIG_RE.finditer(source):
982
+ sig_text = m.group(0).strip()
983
+ name_match = re.search(r'(\w+)\s*\($', sig_text)
984
+ if not name_match:
985
+ name_match = re.search(r'(\w+)\s*\(', sig_text)
986
+ if not name_match:
987
+ continue
988
+ mname = name_match.group(1)
989
+ if mname in class_names or mname in seen_methods or mname in {"if", "for", "while", "switch"}:
990
+ continue
991
+ seen_methods.add(mname)
992
+ functions.append(FunctionSignature(
993
+ name=mname,
994
+ signature=sig_text,
995
+ async_=False,
996
+ exported=True,
997
+ return_type=None,
998
+ ))
999
+
1000
+ # Import statements
1001
+ seen_sources: set[str] = set()
1002
+ for m in _JAVA_IMPORT_RE.finditer(source):
1003
+ full_import = m.group(1).strip()
1004
+ if full_import not in seen_sources:
1005
+ seen_sources.add(full_import)
1006
+ imports.append(ImportRecord(source=full_import, kind="named", symbols=[]))
1007
+
1008
+ # External deps: top-2 package segments, skip java.* / javax.*
1009
+ deps = sorted({
1010
+ ".".join(imp.source.split(".")[:2])
1011
+ for imp in imports
1012
+ if not imp.source.startswith("java.") and not imp.source.startswith("javax.")
1013
+ and len(imp.source.split(".")) >= 2
1014
+ })
1015
+
1016
+ return FileContract(
1017
+ path=path,
1018
+ language="java",
1019
+ exports=exports,
1020
+ imports=sorted(imports, key=lambda i: i.source)[:30],
1021
+ functions=sorted(functions, key=lambda f: f.name)[:20],
1022
+ types=sorted(types, key=lambda t: t.name),
1023
+ dependencies=deps[:20],
1024
+ extraction_method="heuristic",
1025
+ )
1026
+
1027
+
941
1028
  # ---------------------------------------------------------------------------
942
1029
  # Role detection
943
1030
  # ---------------------------------------------------------------------------
@@ -1048,6 +1135,8 @@ class AstExtractor:
1048
1135
 
1049
1136
  if language == "python":
1050
1137
  contract = _extract_python(rel_path, source)
1138
+ elif language == "java":
1139
+ contract = _extract_java(rel_path, source)
1051
1140
  else:
1052
1141
  if self._ensure_ts():
1053
1142
  lang_obj = _get_ts_lang(language)
sourcecode/cli.py CHANGED
@@ -790,7 +790,7 @@ def main(
790
790
  # Require at least 8: src(1)+main(2)+java(3)+com(4)+co(5)+app(6)+module(7)+file.
791
791
  _java_manifest_names = {"pom.xml", "build.gradle", "build.gradle.kts"}
792
792
  _is_java = any(Path(m).name in _java_manifest_names for m in manifests)
793
- _java_min_depth = 8
793
+ _java_min_depth = 10
794
794
  effective_depth = max(depth, _java_min_depth) if _is_java and depth < _java_min_depth else depth
795
795
 
796
796
  # --agent: enable signal analyzers; output via agent_view (not compact)
@@ -1270,10 +1270,24 @@ def main(
1270
1270
  and d.scope not in {"dev"}
1271
1271
  ]
1272
1272
 
1273
- def _dep_sort_key(d: Any) -> tuple[int, int, str]:
1273
+ _JAVA_SEMANTIC_PRIORITY: dict[str, int] = {
1274
+ "spring-boot": 0, "spring-security": 1, "mybatis": 2,
1275
+ "poi": 3, "pdfbox": 4, "jackson": 5, "jjwt": 6,
1276
+ }
1277
+
1278
+ def _java_priority(d: Any) -> int:
1279
+ if d.ecosystem != "java":
1280
+ return 99
1281
+ art = (d.name.split(":")[-1] if ":" in d.name else d.name).lower()
1282
+ for key, pri in _JAVA_SEMANTIC_PRIORITY.items():
1283
+ if key in art:
1284
+ return pri
1285
+ return 50
1286
+
1287
+ def _dep_sort_key(d: Any) -> tuple[int, int, int, str]:
1274
1288
  role_order = _ROLE_PRIORITY.get(d.role or "runtime", 5)
1275
1289
  eco_order = 0 if d.ecosystem == primary_ecosystem else 1
1276
- return (role_order, eco_order, d.name.lower())
1290
+ return (role_order, eco_order, _java_priority(d), d.name.lower())
1277
1291
 
1278
1292
  _seen_dep_names: set[str] = set()
1279
1293
  _deduped_deps: list[Any] = []
@@ -1281,7 +1295,7 @@ def main(
1281
1295
  if d.name not in _seen_dep_names:
1282
1296
  _seen_dep_names.add(d.name)
1283
1297
  _deduped_deps.append(d)
1284
- sm.key_dependencies = _deduped_deps[:15]
1298
+ sm.key_dependencies = _deduped_deps # no cap — all direct deps included
1285
1299
 
1286
1300
  # LQN-02: deterministic NL summary
1287
1301
  sm.project_summary = ProjectSummarizer(target).generate(sm)
@@ -1376,8 +1390,15 @@ def main(
1376
1390
  ))
1377
1391
  sm = _replace(sm, pipeline_trace=_trace.build_trace())
1378
1392
 
1393
+ # P3-B: Auto-switch to centrality ranking when DDD layout detected
1394
+ if (rank_by == "relevance"
1395
+ and sm.architecture is not None
1396
+ and sm.architecture.pattern == "ddd"):
1397
+ rank_by = "centrality"
1398
+
1379
1399
  # Contract pipeline — runs for mode=contract|standard|deep|hybrid (skip for raw)
1380
1400
  _is_contract_mode = mode in ("contract", "standard")
1401
+ _pipeline_error = False
1381
1402
  if _is_contract_mode:
1382
1403
  from sourcecode.contract_pipeline import ContractPipeline
1383
1404
  from sourcecode.contract_model import ContractSummary as _ContractSummary
@@ -1402,6 +1423,7 @@ def main(
1402
1423
  )
1403
1424
  except Exception as _exc:
1404
1425
  typer.echo(f"[error] contract pipeline failed: {_exc}", err=True)
1426
+ _pipeline_error = True
1405
1427
  _contracts = []
1406
1428
  _contract_summary = _ContractSummary(
1407
1429
  mode=mode,
@@ -1512,6 +1534,9 @@ def main(
1512
1534
  # 6. Write output (CLI-04)
1513
1535
  write_output(content, output=output)
1514
1536
 
1537
+ if _pipeline_error:
1538
+ raise typer.Exit(code=2)
1539
+
1515
1540
  # 7. Clipboard copy (--copy / -c)
1516
1541
  if copy and output is None:
1517
1542
  _trimmed = content.strip()
@@ -193,6 +193,27 @@ class ConfidenceAnalyzer:
193
193
  impact="low",
194
194
  ))
195
195
 
196
+ # ── Java test coverage gap check (P2-A) ──────────────────────────────
197
+ _java_all = [p for p in sm.file_paths if p.endswith(".java")]
198
+ _java_tests = [
199
+ p for p in _java_all
200
+ if "/test/" in p.replace("\\", "/") or "/tests/" in p.replace("\\", "/")
201
+ or Path(p).stem.endswith(("Test", "Tests", "IT", "Spec"))
202
+ ]
203
+ _java_prod = [p for p in _java_all if p not in set(_java_tests)]
204
+ if _java_prod and len(_java_prod) >= 10:
205
+ _ratio = len(_java_tests) / len(_java_prod)
206
+ if _ratio < 0.05:
207
+ gaps.append(AnalysisGap(
208
+ area="testing",
209
+ reason=(
210
+ f"Backend test coverage critical: {len(_java_tests)} test files "
211
+ f"for {len(_java_prod)} Java files "
212
+ f"({_ratio:.1%})"
213
+ ),
214
+ impact="high",
215
+ ))
216
+
196
217
  # ── Compute overall confidence ─────────────────────────────────────────
197
218
  # Stack: use best manifest-detected stack, fall back to min
198
219
  manifest_stacks = [s for s in sm.stacks if s.detection_method != "heuristic"]
@@ -370,7 +370,15 @@ class ContractPipeline:
370
370
  """
371
371
  candidates = _find_symbol_files(root, symbol, known_paths, engine)
372
372
  if not candidates:
373
- return []
373
+ return [], {
374
+ "symbol": symbol,
375
+ "definers_found": 0,
376
+ "importers_found": 0,
377
+ "importers_returned": 0,
378
+ "references_found": 0,
379
+ "total_returned": 0,
380
+ "truncated": False,
381
+ }
374
382
 
375
383
  extra: list[FileContract] = []
376
384
  for rel_path in candidates[:300]: # cap to prevent excessive extraction
@@ -577,7 +585,7 @@ def _find_symbol_files(
577
585
  "grep", "-rl",
578
586
  "--include=*.ts", "--include=*.tsx",
579
587
  "--include=*.js", "--include=*.jsx",
580
- "--include=*.py",
588
+ "--include=*.py", "--include=*.java",
581
589
  symbol, ".",
582
590
  ],
583
591
  cwd=str(root),
@@ -127,6 +127,22 @@ def _infer_role(name: str, ecosystem: str, scope: str) -> str:
127
127
  return "infra"
128
128
  return "runtime"
129
129
 
130
+ if ecosystem == "java":
131
+ artifact = n.split(":")[-1] if ":" in n else n
132
+ if any(x in artifact for x in ("spring-boot", "spring-security")):
133
+ return "runtime"
134
+ if any(x in artifact for x in ("spring-web", "spring-mvc", "spring-core", "spring-context")):
135
+ return "runtime"
136
+ if any(x in artifact for x in ("mybatis", "hibernate", "jpa", "druid", "datasource")):
137
+ return "infra"
138
+ if any(x in artifact for x in ("jackson", "gson", "fastjson")):
139
+ return "serialization"
140
+ if any(x in artifact for x in ("poi", "pdfbox", "itext", "openpdf")):
141
+ return "parsing"
142
+ if any(x in artifact for x in ("jjwt", "nimbus-jose")):
143
+ return "runtime"
144
+ return "devtool" if is_dev else "runtime"
145
+
130
146
  return "devtool" if is_dev else "runtime"
131
147
 
132
148
 
@@ -25,8 +25,18 @@ _CONTROLLER_ADVICE_RE = re.compile(r'@ControllerAdvice\b')
25
25
  _WEB_FILTER_RE = re.compile(r'@WebFilter\b')
26
26
  _FILTER_BEAN_RE = re.compile(r'FilterRegistrationBean\b')
27
27
  # Extracts path from @RequestMapping("/v1/foo"), @GetMapping("/bar"), etc.
28
+ # Handles attribute order: value= may come after method= in legacy @RequestMapping style.
28
29
  _HTTP_PATH_RE = re.compile(
29
- r'@(?:Request|Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']'
30
+ r'@(?:Request|Get|Post|Put|Delete|Patch)Mapping\s*\([^)]*?(?:value\s*=\s*)?["\']([^"\']+)["\']'
31
+ )
32
+ _REQUEST_METHOD_VERB_RE = re.compile(
33
+ r'method\s*=\s*RequestMethod\.([A-Z]+)'
34
+ )
35
+ # @M3FiltroSeguridad custom security annotation
36
+ _M3_FILTRO_RE = re.compile(r'@M3FiltroSeguridad\b')
37
+ _M3_FILTRO_PARAMS_RE = re.compile(
38
+ r'@M3FiltroSeguridad\s*\(\s*(?:nombreRecurso\s*=\s*"([^"]*)")?'
39
+ r'(?:[^)]*nivelRequerido\s*=\s*(\d+))?'
30
40
  )
31
41
 
32
42
 
@@ -149,16 +159,29 @@ class JavaDetector(AbstractDetector):
149
159
 
150
160
  # Quick pre-filter before running regexes
151
161
  if ("Controller" not in content and "Filter" not in content
152
- and "ControllerAdvice" not in content):
162
+ and "ControllerAdvice" not in content
163
+ and "M3FiltroSeguridad" not in content):
153
164
  return []
154
165
 
155
166
  if _REST_CONTROLLER_RE.search(content):
156
167
  http_path_match = _HTTP_PATH_RE.search(content)
157
168
  http_path = http_path_match.group(1) if http_path_match else None
169
+ verb_match = _REQUEST_METHOD_VERB_RE.search(content)
170
+ if verb_match and http_path:
171
+ http_path = f"[{verb_match.group(1)}] {http_path}"
172
+ elif verb_match:
173
+ http_path = f"[{verb_match.group(1)}]"
174
+ security_evidence = None
175
+ m3_match = _M3_FILTRO_PARAMS_RE.search(content)
176
+ if m3_match:
177
+ nombre = m3_match.group(1) or ""
178
+ nivel = m3_match.group(2) or ""
179
+ security_evidence = f"@M3FiltroSeguridad(nombreRecurso={nombre!r}, nivelRequerido={nivel})"
158
180
  return [EntryPoint(
159
181
  path=rel_path, stack="java", kind="rest_controller",
160
182
  source="annotation", confidence="high",
161
183
  http_path=http_path,
184
+ evidence=security_evidence,
162
185
  )]
163
186
  if _CONTROLLER_ADVICE_RE.search(content):
164
187
  return [EntryPoint(
@@ -168,10 +191,22 @@ class JavaDetector(AbstractDetector):
168
191
  if _MVC_CONTROLLER_RE.search(content) and _REQUEST_MAPPING_RE.search(content):
169
192
  http_path_match = _HTTP_PATH_RE.search(content)
170
193
  http_path = http_path_match.group(1) if http_path_match else None
194
+ verb_match = _REQUEST_METHOD_VERB_RE.search(content)
195
+ if verb_match and http_path:
196
+ http_path = f"[{verb_match.group(1)}] {http_path}"
197
+ elif verb_match:
198
+ http_path = f"[{verb_match.group(1)}]"
199
+ security_evidence = None
200
+ m3_match = _M3_FILTRO_PARAMS_RE.search(content)
201
+ if m3_match:
202
+ nombre = m3_match.group(1) or ""
203
+ nivel = m3_match.group(2) or ""
204
+ security_evidence = f"@M3FiltroSeguridad(nombreRecurso={nombre!r}, nivelRequerido={nivel})"
171
205
  return [EntryPoint(
172
206
  path=rel_path, stack="java", kind="mvc_controller",
173
207
  source="annotation", confidence="medium",
174
208
  http_path=http_path,
209
+ evidence=security_evidence,
175
210
  )]
176
211
  if _WEB_FILTER_RE.search(content):
177
212
  return [EntryPoint(
@@ -174,6 +174,14 @@ class DocAnalyzer:
174
174
  limitations.extend(file_limitations)
175
175
  if file_records:
176
176
  languages.add(lang)
177
+ elif suffix == ".java":
178
+ file_records, file_limitations = self._analyze_java_file(
179
+ norm_path, content, depth, workspace, entry_points
180
+ )
181
+ records.extend(file_records)
182
+ limitations.extend(file_limitations)
183
+ if file_records:
184
+ languages.add("java")
177
185
  else:
178
186
  # Unsupported language — D-04: no emitir DocRecord, solo registrar limitation
179
187
  limitations.append(f"docs_unavailable:{norm_path}:language={lang}")
@@ -182,7 +190,7 @@ class DocAnalyzer:
182
190
  # NO records.append() here
183
191
 
184
192
  # Build language_coverage: explicit per-language support status
185
- _SUPPORTED_LANGS = {"python", "javascript", "typescript"}
193
+ _SUPPORTED_LANGS = {"python", "javascript", "typescript", "java"}
186
194
  lang_coverage: dict[str, str] = {}
187
195
  for lang in languages:
188
196
  if lang in _SUPPORTED_LANGS:
@@ -225,6 +233,73 @@ class DocAnalyzer:
225
233
  )
226
234
  return records, summary
227
235
 
236
+ # Javadoc: /** ... */ block followed by class/method declaration
237
+ _JAVADOC_RE = re.compile(r'/\*\*(.*?)\*/', re.DOTALL)
238
+ _JAVA_CLASS_AFTER_RE = re.compile(
239
+ r'(?:public\s+)?(?:abstract\s+)?(?:final\s+)?(?:class|interface|enum)\s+(\w+)',
240
+ )
241
+ _JAVA_METHOD_AFTER_RE = re.compile(
242
+ r'public\s+[\w<>\[\],\s]+?\s+(\w+)\s*\(',
243
+ )
244
+
245
+ def _clean_javadoc(self, raw: str) -> str:
246
+ lines = raw.strip().splitlines()
247
+ cleaned: list[str] = []
248
+ for line in lines:
249
+ line = re.sub(r'^\s*\*\s?', '', line).strip()
250
+ if line.startswith('@'):
251
+ continue
252
+ if line:
253
+ cleaned.append(line)
254
+ result = ' '.join(cleaned)
255
+ if len(result) > self._DOCSTRING_MAX_CHARS:
256
+ result = result[:self._DOCSTRING_MAX_CHARS] + self._TRUNCATION_SUFFIX
257
+ return result
258
+
259
+ def _analyze_java_file(
260
+ self,
261
+ path: str,
262
+ content: str,
263
+ depth: "DocsDepth",
264
+ workspace: "str | None",
265
+ entry_points: "list[str] | None",
266
+ ) -> "tuple[list[DocRecord], list[str]]":
267
+ records: list[DocRecord] = []
268
+ for jd_match in self._JAVADOC_RE.finditer(content):
269
+ lookahead = content[jd_match.end():jd_match.end() + 400].lstrip()
270
+ # Class-level Javadoc
271
+ class_m = self._JAVA_CLASS_AFTER_RE.match(lookahead)
272
+ if class_m:
273
+ name = class_m.group(1)
274
+ doc_text = self._clean_javadoc(jd_match.group(1))
275
+ records.append(DocRecord(
276
+ path=path,
277
+ workspace=workspace,
278
+ kind="class",
279
+ name=name,
280
+ doc_text=doc_text,
281
+ importance=self._infer_importance(path, "class", entry_points),
282
+ ))
283
+ continue
284
+ # Method-level Javadoc (only when depth != "module")
285
+ if depth == "module":
286
+ continue
287
+ method_m = self._JAVA_METHOD_AFTER_RE.match(lookahead)
288
+ if method_m:
289
+ name = method_m.group(1)
290
+ if name in {"if", "for", "while", "switch", "return", "new"}:
291
+ continue
292
+ doc_text = self._clean_javadoc(jd_match.group(1))
293
+ records.append(DocRecord(
294
+ path=path,
295
+ workspace=workspace,
296
+ kind="function",
297
+ name=name,
298
+ doc_text=doc_text,
299
+ importance=self._infer_importance(path, "function", entry_points),
300
+ ))
301
+ return records, []
302
+
228
303
  def merge_summaries(self, summaries: Iterable[DocSummary]) -> DocSummary:
229
304
  """Agrega multiples DocSummary en uno.
230
305
 
@@ -67,6 +67,9 @@ def runtime_relevance(ep: EntryPoint, classification: Classification | None = No
67
67
  classification = classification or classify_entry_point(ep)
68
68
  if classification != "production":
69
69
  return "low"
70
+ # Annotation-detected HTTP controllers are the primary runtime surface
71
+ if ep.source == "annotation" and ep.kind in {"rest_controller", "mvc_controller"}:
72
+ return "high"
70
73
  reason = (ep.reason or "").lower()
71
74
  if ep.source == "package.json#bin" or reason == "bin" or reason in _PRODUCTION_SCRIPT_REASONS:
72
75
  return "high"
@@ -27,6 +27,8 @@ _ENV_EXAMPLE_NAMES = {
27
27
 
28
28
  # Spring Boot application.properties / application.yml and their profile variants
29
29
  _SPRING_CONF_BASE = {"application.properties", "application.yml", "application.yaml"}
30
+ # Matches options/{profile}/ in multi-tenant SAS layout paths
31
+ _OPTIONS_PROFILE_PATH_RE = re.compile(r'options/([a-z0-9_-]+)/', re.IGNORECASE)
30
32
  _SPRING_CONF_PROFILE_RE = re.compile(r'^application-([a-z0-9_-]+)\.(properties|ya?ml)$', re.IGNORECASE)
31
33
  # Matches ${ENV_VAR} or ${ENV_VAR:default} where ENV_VAR is UPPER_SNAKE_CASE.
32
34
  # Group 1 = key, Group 2 = default (may be empty string, absent = no default).
@@ -507,6 +509,10 @@ class EnvAnalyzer:
507
509
  # Spring Boot application.properties / application.yml (incl. profiles)
508
510
  if name_lower in _SPRING_CONF_BASE or _SPRING_CONF_PROFILE_RE.match(name_lower):
509
511
  profile = _extract_spring_profile(name)
512
+ # Override profile if path contains options/{profile}/ (multi-tenant SAS layout)
513
+ path_profile_match = _OPTIONS_PROFILE_PATH_RE.search(rel)
514
+ if path_profile_match:
515
+ profile = path_profile_match.group(1)
510
516
  if profile and profile not in profiles_scanned:
511
517
  profiles_scanned.append(profile)
512
518
  count = _parse_spring_config(entry, rel, findings, profile)
@@ -76,7 +76,7 @@ class GraphAnalyzer:
76
76
  def __init__(
77
77
  self,
78
78
  *,
79
- max_files: int = 200,
79
+ max_files: int = 2000,
80
80
  max_file_size: int = 200_000,
81
81
  max_nodes: int = 1_000,
82
82
  max_edges: int = 2_000,
@@ -137,7 +137,7 @@ def _mccabe(func_node: ast.FunctionDef | ast.AsyncFunctionDef) -> int:
137
137
  class MetricsAnalyzer:
138
138
  """Analiza metricas de calidad de codigo: LOC, simbolos y complejidad ciclomatica."""
139
139
 
140
- _MAX_FILES = 500
140
+ _MAX_FILES = 2000
141
141
  _MAX_FILE_SIZE = 500_000 # bytes
142
142
 
143
143
  # ---------------------------------------------------------------------------
@@ -159,6 +159,21 @@ class MetricsAnalyzer:
159
159
  # Keep only paths that are actual files (not directories)
160
160
  file_paths = [p for p in all_paths if (root / p).is_file()]
161
161
 
162
+ # Sort: JVM source first, then other source, then config, then dotfiles
163
+ # Prevents the 500-file cap from cutting all Java files when dotfiles sort first.
164
+ def _sort_key(p: str) -> tuple[int, str]:
165
+ if Path(p).suffix.lower() in {".java", ".kt", ".scala"}:
166
+ return (0, p)
167
+ if Path(p).suffix.lower() in {".py", ".go", ".rs", ".ts", ".js", ".tsx", ".jsx"}:
168
+ return (1, p)
169
+ if Path(p).suffix.lower() in {".xml", ".yaml", ".yml", ".json", ".toml", ".properties"}:
170
+ return (2, p)
171
+ if Path(p).name.startswith("."):
172
+ return (4, p)
173
+ return (3, p)
174
+
175
+ file_paths.sort(key=_sort_key)
176
+
162
177
  limitations: list[str] = []
163
178
 
164
179
  # Guard: max files
@@ -229,6 +244,29 @@ class MetricsAnalyzer:
229
244
  "null complexity fields are expected, not an error."
230
245
  )
231
246
 
247
+ # P2-C: DDD module metrics — group by module, count files/methods per layer
248
+ _DDD_LAYERS = {"domain", "application", "infrastructure"}
249
+ ddd_files = [r for r in records if "/ddd/" in r.path.replace("\\", "/")]
250
+ if ddd_files:
251
+ module_layer_data: dict[str, dict[str, dict]] = {}
252
+ for fm in ddd_files:
253
+ parts = fm.path.replace("\\", "/").split("/")
254
+ for i, part in enumerate(parts):
255
+ if part in _DDD_LAYERS and i >= 2:
256
+ module = parts[i - 1]
257
+ layer = part
258
+ if module not in module_layer_data:
259
+ module_layer_data[module] = {lyr: {"files": 0, "methods": 0} for lyr in _DDD_LAYERS}
260
+ module_layer_data[module][layer]["files"] += 1
261
+ module_layer_data[module][layer]["methods"] += fm.function_count or 0
262
+ break
263
+ ddd_metrics = [
264
+ {"module": mod, "layers": layers}
265
+ for mod, layers in sorted(module_layer_data.items())
266
+ ]
267
+ else:
268
+ ddd_metrics = []
269
+
232
270
  summary = MetricsSummary(
233
271
  requested=True,
234
272
  file_count=len(records),
@@ -238,6 +276,7 @@ class MetricsAnalyzer:
238
276
  coverage_records=coverage_records,
239
277
  coverage_sources_found=sorted({r.format for r in coverage_records}),
240
278
  limitations=limitations,
279
+ ddd_module_metrics=ddd_metrics,
241
280
  )
242
281
  return records, summary
243
282
 
@@ -760,6 +760,33 @@ class TaskContextBuilder:
760
760
  elif self._is_source(path) and not content_reasons:
761
761
  content_boost += 0.5
762
762
 
763
+ # Task-specific boosts for differentiated file weighting
764
+ path_lower = path.lower()
765
+ if task_name == "fix-bug":
766
+ if any(x in path_lower for x in ("exception", "error", "handler", "advice")):
767
+ content_boost += 1.5
768
+ content_reasons.append("exception handler — high risk area")
769
+ elif task_name == "generate-tests":
770
+ stem = Path(path).stem.lower()
771
+ has_test = any(
772
+ stem in Path(tp).stem.lower() or Path(tp).stem.lower() in stem
773
+ for tp in test_set
774
+ )
775
+ if not has_test and self._is_source(path):
776
+ content_boost += 1.0
777
+ content_reasons.append("no test pair found")
778
+ elif task_name == "onboard":
779
+ if path in runtime_entry_set:
780
+ content_boost += 2.0
781
+ content_reasons.append("runtime entry point")
782
+ if any(x in path_lower for x in ("config", "application.yml", "application.properties", "settings", "bootstrap")):
783
+ content_boost += 1.0
784
+ content_reasons.append("configuration class")
785
+ elif task_name == "explain":
786
+ if "controller" in path_lower and path in runtime_entry_set:
787
+ content_boost += 1.5
788
+ content_reasons.append("DDD module controller")
789
+
763
790
  total = fs.score + content_boost
764
791
  if total <= 0:
765
792
  continue
sourcecode/schema.py CHANGED
@@ -225,6 +225,7 @@ class MetricsSummary:
225
225
  coverage_records: list[CoverageRecord] = field(default_factory=list)
226
226
  coverage_sources_found: list[str] = field(default_factory=list)
227
227
  limitations: list[str] = field(default_factory=list)
228
+ ddd_module_metrics: list[dict] = field(default_factory=list)
228
229
 
229
230
 
230
231
  @dataclass
@@ -40,6 +40,34 @@ _MAX_SYMBOLS = 10_000
40
40
  # JS/TS keyword and builtin exclusions (Plan 12-03)
41
41
  # ---------------------------------------------------------------------------
42
42
 
43
+ # ---------------------------------------------------------------------------
44
+ # Java/JVM heuristic regex constants (module-level for performance)
45
+ # ---------------------------------------------------------------------------
46
+
47
+ _AUTOWIRED_FIELD_RE = re.compile(
48
+ r'@Autowired\s+(?:private\s+|protected\s+|public\s+)?([A-Z][A-Za-z0-9_<>]*)\s+(\w+)\s*;'
49
+ )
50
+ _MAPPER_IFACE_RE = re.compile(
51
+ r'@Mapper\b.*?(?:public\s+)?interface\s+([A-Z][A-Za-z0-9_]*)',
52
+ re.DOTALL,
53
+ )
54
+ _EXTENDS_RE = re.compile(
55
+ r'(?:class|interface)\s+([A-Z][A-Za-z0-9_]*)\s+extends\s+([A-Z][A-Za-z0-9_]*)'
56
+ )
57
+ _M3_FILTRO_METHOD_RE = re.compile(
58
+ r'@M3FiltroSeguridad(?:\([^)]*\))?\s+(?:@[^\s]+\s+)*'
59
+ r'(?:public|private|protected)\s+\w[\w<>\[\]]*\s+([a-z][A-Za-z0-9_]*)\s*\('
60
+ )
61
+ _LOMBOK_CLASS_RE = re.compile(
62
+ r'(@(?:Data|Slf4j|Builder|AllArgsConstructor|NoArgsConstructor)(?:\([^)]*\))?\s+)*'
63
+ r'(?:public\s+)?(?:class|interface)\s+([A-Z][A-Za-z0-9_]*)',
64
+ re.MULTILINE,
65
+ )
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # JS/TS keyword and builtin exclusions (Plan 12-03)
69
+ # ---------------------------------------------------------------------------
70
+
43
71
  _JS_KEYWORD_EXCLUSIONS: frozenset[str] = frozenset({
44
72
  # JS reserved words
45
73
  "if", "else", "for", "while", "do", "switch", "case", "break", "continue",
@@ -645,6 +673,29 @@ class SemanticAnalyzer:
645
673
  ),
646
674
  }
647
675
 
676
+ # P1-D: Link @Mapper interfaces to their *Mapper.xml files
677
+ xml_paths = [p for p in all_paths if p.endswith(("Mapper.xml", "MyBatis.xml"))]
678
+ mapper_xml_index: dict[str, str] = {}
679
+ for xp in xml_paths:
680
+ stem = Path(xp).stem # e.g. "PacienteMapper"
681
+ mapper_xml_index[stem] = xp
682
+
683
+ for sym in all_symbols:
684
+ if sym.kind == "mapper_interface" and sym.language == "java":
685
+ xml_key = sym.symbol
686
+ if xml_key in mapper_xml_index:
687
+ xml_path = mapper_xml_index[xml_key]
688
+ links.append(SymbolLink(
689
+ importer_path=Path(sym.path).as_posix(),
690
+ symbol=sym.symbol,
691
+ source_path=xml_path,
692
+ source_line=None,
693
+ is_external=False,
694
+ confidence="high",
695
+ method="heuristic",
696
+ workspace=workspace,
697
+ ))
698
+
648
699
  # Determine explicit analysis status — never emit silent empty results.
649
700
  # An agent must be able to tell "analysis ran and found nothing" from
650
701
  # "analysis failed to run" or "significant coverage gap".
@@ -865,6 +916,8 @@ class SemanticAnalyzer:
865
916
  """Heuristic Java/Kotlin: detecta class/method declarations y call sites.
866
917
 
867
918
  method="heuristic", confidence="low" para todos los edges Java.
919
+ Includes: Lombok synthetic symbols, @Autowired field edges,
920
+ @Mapper interface detection, inheritance chains, @M3FiltroSeguridad AOP edges.
868
921
  """
869
922
  _JAVA_KEYWORDS: frozenset[str] = frozenset({
870
923
  "if", "for", "while", "switch", "catch", "super", "this", "new",
@@ -923,6 +976,105 @@ class SemanticAnalyzer:
923
976
  method="heuristic",
924
977
  ))
925
978
 
979
+ # P1-D: @Mapper interface detection — emit as mapper_interface symbol
980
+ for m in _MAPPER_IFACE_RE.finditer(content):
981
+ iface_name = m.group(1)
982
+ line = content[: m.start()].count("\n") + 1
983
+ symbols.append(SymbolRecord(
984
+ symbol=iface_name,
985
+ kind="mapper_interface",
986
+ language="java",
987
+ path=rel_path,
988
+ line=line,
989
+ exported=True,
990
+ ))
991
+
992
+ # P1-E: Inheritance chain — class X extends Y → edge X→Y
993
+ for m in _EXTENDS_RE.finditer(content):
994
+ subclass = m.group(1)
995
+ superclass = m.group(2)
996
+ line = content[: m.start()].count("\n") + 1
997
+ calls.append(CallRecord(
998
+ caller_path=rel_path,
999
+ caller_symbol=subclass,
1000
+ callee_path=rel_path,
1001
+ callee_symbol=superclass,
1002
+ call_line=line,
1003
+ confidence="low",
1004
+ method="heuristic",
1005
+ ))
1006
+
1007
+ # P1-F: Lombok annotations — emit synthetic symbols
1008
+ for m in _LOMBOK_CLASS_RE.finditer(content):
1009
+ annotations_block = m.group(0)
1010
+ class_name = m.group(2)
1011
+ line = content[: m.start()].count("\n") + 1
1012
+ if "@Slf4j" in annotations_block:
1013
+ symbols.append(SymbolRecord(
1014
+ symbol="log",
1015
+ kind="field",
1016
+ language="java",
1017
+ path=rel_path,
1018
+ line=line,
1019
+ exported=False,
1020
+ ))
1021
+ if "@Builder" in annotations_block:
1022
+ symbols.append(SymbolRecord(
1023
+ symbol=f"{class_name}.builder",
1024
+ kind="function",
1025
+ language="java",
1026
+ path=rel_path,
1027
+ line=line,
1028
+ exported=True,
1029
+ ))
1030
+ if "@AllArgsConstructor" in annotations_block or "@Data" in annotations_block:
1031
+ symbols.append(SymbolRecord(
1032
+ symbol=f"{class_name}(allArgs)",
1033
+ kind="function",
1034
+ language="java",
1035
+ path=rel_path,
1036
+ line=line,
1037
+ exported=True,
1038
+ ))
1039
+ if "@NoArgsConstructor" in annotations_block:
1040
+ symbols.append(SymbolRecord(
1041
+ symbol=f"{class_name}()",
1042
+ kind="function",
1043
+ language="java",
1044
+ path=rel_path,
1045
+ line=line,
1046
+ exported=True,
1047
+ ))
1048
+
1049
+ # P1-G: @Autowired field injection — emit dependency edges
1050
+ for m in _AUTOWIRED_FIELD_RE.finditer(content):
1051
+ field_type = m.group(1)
1052
+ field_name = m.group(2)
1053
+ line = content[: m.start()].count("\n") + 1
1054
+ calls.append(CallRecord(
1055
+ caller_path=rel_path,
1056
+ caller_symbol=field_name,
1057
+ callee_path=rel_path,
1058
+ callee_symbol=field_type,
1059
+ call_line=line,
1060
+ confidence="medium",
1061
+ method="heuristic",
1062
+ ))
1063
+
1064
+ # P3-C: @M3FiltroSeguridad AOP proxy edges
1065
+ for m in _M3_FILTRO_METHOD_RE.finditer(content):
1066
+ method_name = m.group(1)
1067
+ line = content[: m.start()].count("\n") + 1
1068
+ calls.append(CallRecord(
1069
+ caller_path=rel_path,
1070
+ caller_symbol="M3FiltroSeguridadImpl",
1071
+ callee_path=rel_path,
1072
+ callee_symbol=method_name,
1073
+ call_line=line,
1074
+ confidence="medium",
1075
+ method="heuristic",
1076
+ ))
1077
+
926
1078
  return symbols, calls
927
1079
 
928
1080
  # -----------------------------------------------------------------------
sourcecode/serializer.py CHANGED
@@ -35,7 +35,7 @@ _FILE_RELEVANCE_MIN_COMBINED = 0.40 # minimum combined score — must earn incl
35
35
  _PROD_DEPS_CAP = 10 # max production dependencies shown
36
36
  _SECONDARY_DEPS_CAP = 5 # max per dev/test/build dependency group
37
37
  _MONOREPO_PKGS_CAP = 8 # max workspace/runtime packages shown
38
- _KEY_DEPS_CAP = 10 # max key dependencies shown
38
+ _KEY_DEPS_CAP = 50 # max key dependencies shown
39
39
  _CODE_NOTES_CAP = 15 # max code notes in default output
40
40
  _ENV_MAP_CAP = 15 # max env var entries in default output
41
41
 
@@ -223,6 +223,11 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
223
223
  semantic_hub_scores[p] = h.get("importance_score", 0.0) / max_importance
224
224
 
225
225
  entry_paths = {ep.path for ep in sm.entry_points}
226
+ # REST/MVC controllers are HTTP surface — surface before @Transactional services
227
+ _rest_ctrl_paths = {
228
+ ep.path for ep in sm.entry_points
229
+ if getattr(ep, "kind", "") in {"rest_controller", "mvc_controller"}
230
+ }
226
231
  scored: list[tuple[float, dict[str, Any]]] = []
227
232
 
228
233
  for path in sm.file_paths:
@@ -241,6 +246,9 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
241
246
  # Semantic hub bonus: normalised call-graph centrality adds up to +0.30
242
247
  sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
243
248
  combined = fs.score + content_rel + sem_hub
249
+ # REST controller boost: surface above @Transactional service files
250
+ if path in _rest_ctrl_paths:
251
+ combined += 2.0
244
252
 
245
253
  # Visibility threshold: require meaningful combined signal.
246
254
  # Exception: high/medium-confidence files with strong content relevance
@@ -1586,8 +1594,8 @@ def write_output(content: str, output: Optional[Path]) -> None:
1586
1594
  output: Destination file path. None = stdout.
1587
1595
  """
1588
1596
  if output is None:
1589
- sys.stdout.write(content)
1597
+ sys.stdout.buffer.write(content.encode("utf-8"))
1590
1598
  if not content.endswith("\n"):
1591
- sys.stdout.write("\n")
1599
+ sys.stdout.buffer.write(b"\n")
1592
1600
  else:
1593
1601
  output.write_text(content, encoding="utf-8")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.1.0
3
+ Version: 1.3.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -1,35 +1,35 @@
1
- sourcecode/__init__.py,sha256=U4-Ic6jRz9YH4wIYlhtl8YFtDO_yG3OsMIWYQbQ3mKE,102
1
+ sourcecode/__init__.py,sha256=OFSkiae_6W3SLXW8WqBF-PGOUxC4FhKuxuKQDTuCuvc,102
2
2
  sourcecode/adaptive_scanner.py,sha256=6dh34C2qZXyRbw-8xBhbEwDdXanM6CRFRWayVoYITnA,10190
3
- sourcecode/architecture_analyzer.py,sha256=hn2K4c_EknGehXZ3I1KyoJPI-LlBSkphrVGBZMceif4,31249
3
+ sourcecode/architecture_analyzer.py,sha256=qzDW3_lQv__czQ-qs6AqEEoMvTfhfp7M7kNslPuQy7A,32128
4
4
  sourcecode/architecture_summary.py,sha256=J9yoLgh8wXwIRrT6q6JooB6PekivbOEYpJz4BUXdalk,20545
5
- sourcecode/ast_extractor.py,sha256=0OHQwTUBBc9lmqPLryVeB1z8dGIC6NhLlar800CD9oI,41129
5
+ sourcecode/ast_extractor.py,sha256=zvHeO-w0evdS8EAJhwlK7hhrWMGN-lnHm6XFdZlaST8,44389
6
6
  sourcecode/classifier.py,sha256=GKTMN8qKZX7ponSwDJfN08RrasI4CVpq1_gFBgEopps,7093
7
- sourcecode/cli.py,sha256=YusMOF5OfihL3nBw66LcANRFSiVHugPrXE0vPIycjLQ,72016
7
+ sourcecode/cli.py,sha256=0g7U2s1pyoTqFc7ne62Z305bErRKuHuKGyN0VrlsSOk,72971
8
8
  sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
9
- sourcecode/confidence_analyzer.py,sha256=HxJMPLI5ulqtkncnv98W4iVO6yMbpQo87VuxiuNbDmY,12167
9
+ sourcecode/confidence_analyzer.py,sha256=HcaewB2pZaZ_hfKrZWtr_yPMY2-CxS1zzTUD7c4argc,13188
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=gCf9-Kj0G7l0lvRTAcRfFAfMgs1Rpizv4mKovQLYUkw,3434
13
- sourcecode/contract_pipeline.py,sha256=eann6PZJ8Bg2zavaOhmfN_oy9rMWNSkKPDW68rRZV8E,25686
13
+ sourcecode/contract_pipeline.py,sha256=lDa0MgalW-WPXwbWKX2NB6HWxt_15P6GZNXgAoJeDAE,25985
14
14
  sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
15
- sourcecode/dependency_analyzer.py,sha256=Exq0BfInvfS5iAg9xAr6WI2uPNuotkIudTKcYJcRhB8,52757
16
- sourcecode/doc_analyzer.py,sha256=TttdS7mndKQhyJCfJnnAsyGCJrf-TIL7oXxDlTLUFKE,21248
17
- sourcecode/entrypoint_classifier.py,sha256=a69dMGyxCTd_LOm3oqj-EXWpRmbmeujN7T1mr2eJ1as,3877
18
- sourcecode/env_analyzer.py,sha256=Ifwst0YLvArHHaRQXlf9DCYGO0MdyQBAMWSyEzfpKZo,21650
15
+ sourcecode/dependency_analyzer.py,sha256=EZyBI1-VkYCmPrcIIIpa7WAp888FF9Ct4nY2ronowUg,53555
16
+ sourcecode/doc_analyzer.py,sha256=a1CIClCNmfYM3ku4bdgwHQpmb6Js4wdJZ1V5EYLo04I,24345
17
+ sourcecode/entrypoint_classifier.py,sha256=gvKgl0f5T8ol1r4JMmkeqGHuZTfZJiOwFOWdc7EYwYw,4061
18
+ sourcecode/env_analyzer.py,sha256=GxCidahAAIptTdDFIlVB6URd4HBnBlIX_SqUov3MBRQ,22076
19
19
  sourcecode/file_classifier.py,sha256=48ly5Z6exkzBy8lNy1AkdP4-oJqIA1zT3LZfffuTyDo,11572
20
20
  sourcecode/git_analyzer.py,sha256=PD3eNWydznQ6KLNpxGzBqizIHoPIKevfwz9Xyf_pDt4,11600
21
- sourcecode/graph_analyzer.py,sha256=hMOsLLz9B0UnQ4xwbHdgr3bFvqpw0bQ8kN-xmEn3Krk,64156
22
- sourcecode/metrics_analyzer.py,sha256=e2cFwB9XubFq_dIVsP2PLjpr4wX0N6ulb3ol3sGDUeo,20777
23
- sourcecode/prepare_context.py,sha256=FKh-M5B74r-yztuAgfuSE8RjIZvsq9YRwTr74zmldxI,35901
21
+ sourcecode/graph_analyzer.py,sha256=Ko3nJp_wx92jic6AuFbdkOstDjzl-O62g1zazYJjm9w,64157
22
+ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
23
+ sourcecode/prepare_context.py,sha256=LsFDp7HnHdvtwVa46YUD60uMBfwXaVs4suMfBvc8tyI,37357
24
24
  sourcecode/ranking_engine.py,sha256=virVglafZufioHpZpwktjMvUiL0TZELWQCQnQNV8dFo,9360
25
25
  sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
26
26
  sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
27
27
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
28
28
  sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBGPjQ,14792
29
29
  sourcecode/scanner.py,sha256=aM3h9-DCQ3xKpeHpHYdo2vX6T5P95HA_YwZbkAVNwmo,8288
30
- sourcecode/schema.py,sha256=5s9Gtiw2Fk-HEVwVcegl2fy-cyYBwS16WSTS0xIv744,23204
31
- sourcecode/semantic_analyzer.py,sha256=16EFTgM7ooW0m5gNUKOlTSn7IEMLSzKmzQn-cWaSqjs,82604
32
- sourcecode/serializer.py,sha256=k-rddaaIlvAA5F2qvizCh_yd4oAlhhsg2obrYoJKtlo,65424
30
+ sourcecode/schema.py,sha256=M0aA3sIIm8QHDIAducZUm5mGpgJ7oXfqtyvoMkLGMac,23270
31
+ sourcecode/semantic_analyzer.py,sha256=12TwXYkYbDcBdu0heX_EmfPM2EkO8a_r5osf0SaeQbs,88956
32
+ sourcecode/serializer.py,sha256=-OFruU8NHsmHR_IKOb11VVl3-WOBXKPhyCtM8NZZtb0,65832
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=H5qicYbpIFqThCuT4Aocn-d2zEZ_6vJc-kLjHZITIBw,9084
45
+ sourcecode/detectors/java.py,sha256=Lv-a1YeXezJYZCJduWfeeLMTUgyPz4lyiYu9Edi8-7E,10821
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.1.0.dist-info/METADATA,sha256=pM02mysiHsgFzu3TzkDPTNX78ts7wt0S4R4We6zDA_w,20411
64
- sourcecode-1.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
- sourcecode-1.1.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
66
- sourcecode-1.1.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
67
- sourcecode-1.1.0.dist-info/RECORD,,
63
+ sourcecode-1.3.0.dist-info/METADATA,sha256=5tHuzUw0xpsY6eeCd81OI_pL_vl-3qaMCU78kExd6WI,20411
64
+ sourcecode-1.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
+ sourcecode-1.3.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
66
+ sourcecode-1.3.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
67
+ sourcecode-1.3.0.dist-info/RECORD,,