sourcecode 1.1.0__tar.gz → 1.2.0__tar.gz

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.
Files changed (177) hide show
  1. {sourcecode-1.1.0 → sourcecode-1.2.0}/PKG-INFO +1 -1
  2. {sourcecode-1.1.0 → sourcecode-1.2.0}/pyproject.toml +1 -1
  3. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/__init__.py +1 -1
  4. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/architecture_analyzer.py +21 -1
  5. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/cli.py +7 -1
  6. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/confidence_analyzer.py +21 -0
  7. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/java.py +37 -2
  8. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/env_analyzer.py +6 -0
  9. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/metrics_analyzer.py +24 -0
  10. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/schema.py +1 -0
  11. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/semantic_analyzer.py +152 -0
  12. {sourcecode-1.1.0 → sourcecode-1.2.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  13. {sourcecode-1.1.0 → sourcecode-1.2.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  14. {sourcecode-1.1.0 → sourcecode-1.2.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  15. {sourcecode-1.1.0 → sourcecode-1.2.0}/.github/workflows/build-windows.yml +0 -0
  16. {sourcecode-1.1.0 → sourcecode-1.2.0}/.gitignore +0 -0
  17. {sourcecode-1.1.0 → sourcecode-1.2.0}/.ruff.toml +0 -0
  18. {sourcecode-1.1.0 → sourcecode-1.2.0}/CONTRIBUTING.md +0 -0
  19. {sourcecode-1.1.0 → sourcecode-1.2.0}/LICENSE +0 -0
  20. {sourcecode-1.1.0 → sourcecode-1.2.0}/README.md +0 -0
  21. {sourcecode-1.1.0 → sourcecode-1.2.0}/SECURITY.md +0 -0
  22. {sourcecode-1.1.0 → sourcecode-1.2.0}/docs/privacy.md +0 -0
  23. {sourcecode-1.1.0 → sourcecode-1.2.0}/docs/schema.md +0 -0
  24. {sourcecode-1.1.0 → sourcecode-1.2.0}/raw +0 -0
  25. {sourcecode-1.1.0 → sourcecode-1.2.0}/run_cli.py +0 -0
  26. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/adaptive_scanner.py +0 -0
  27. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/architecture_summary.py +0 -0
  28. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/ast_extractor.py +0 -0
  29. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  31. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/context_scorer.py +0 -0
  32. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/context_summarizer.py +0 -0
  33. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/contract_model.py +0 -0
  34. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/contract_pipeline.py +0 -0
  35. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/coverage_parser.py +0 -0
  36. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/dependency_analyzer.py +0 -0
  37. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/__init__.py +0 -0
  38. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/base.py +0 -0
  39. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  40. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/dart.py +0 -0
  41. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/dotnet.py +0 -0
  42. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/elixir.py +0 -0
  43. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/go.py +0 -0
  44. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/heuristic.py +0 -0
  45. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/hybrid.py +0 -0
  46. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  47. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/nodejs.py +0 -0
  48. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/parsers.py +0 -0
  49. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/php.py +0 -0
  50. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/project.py +0 -0
  51. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/python.py +0 -0
  52. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/ruby.py +0 -0
  53. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/rust.py +0 -0
  54. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/systems.py +0 -0
  55. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/terraform.py +0 -0
  56. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/detectors/tooling.py +0 -0
  57. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/doc_analyzer.py +0 -0
  58. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  59. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/file_classifier.py +0 -0
  60. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/git_analyzer.py +0 -0
  61. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/graph_analyzer.py +0 -0
  62. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/prepare_context.py +0 -0
  63. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/ranking_engine.py +0 -0
  64. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/redactor.py +0 -0
  65. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/relevance_scorer.py +0 -0
  66. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/repo_classifier.py +0 -0
  67. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/runtime_classifier.py +0 -0
  68. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/scanner.py +0 -0
  69. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/serializer.py +0 -0
  70. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/summarizer.py +0 -0
  71. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/__init__.py +0 -0
  72. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/config.py +0 -0
  73. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/consent.py +0 -0
  74. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/events.py +0 -0
  75. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/filters.py +0 -0
  76. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/telemetry/transport.py +0 -0
  77. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/tree_utils.py +0 -0
  78. {sourcecode-1.1.0 → sourcecode-1.2.0}/src/sourcecode/workspace.py +0 -0
  79. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/__init__.py +0 -0
  80. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/conftest.py +0 -0
  81. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/coverage.xml +0 -0
  82. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  83. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  84. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  85. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/go_service/go.mod +0 -0
  86. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/jacoco.xml +0 -0
  87. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/lcov.info +0 -0
  88. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  89. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/nextjs_app/package.json +0 -0
  90. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  91. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  92. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  93. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  94. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  95. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  96. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  97. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  98. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  99. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  100. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  101. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  102. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  103. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  104. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  105. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  106. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  107. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  108. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  109. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  110. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  111. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  112. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  113. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  114. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  115. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  116. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  117. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  118. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  119. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  120. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_architecture_analyzer.py +0 -0
  121. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_architecture_summary.py +0 -0
  122. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_ast_extractor.py +0 -0
  123. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_block1_reliability.py +0 -0
  124. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_block2_coverage.py +0 -0
  125. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_block5_quality.py +0 -0
  126. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_classifier.py +0 -0
  127. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_cli.py +0 -0
  128. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_code_notes_analyzer.py +0 -0
  129. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_context_scorer.py +0 -0
  130. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_contract_pipeline.py +0 -0
  131. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_coverage_parser.py +0 -0
  132. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_cross_consistency.py +0 -0
  133. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  134. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  135. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_dependency_schema.py +0 -0
  136. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_dotnet.py +0 -0
  137. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_go_rust_java.py +0 -0
  138. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_nodejs.py +0 -0
  139. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_php_ruby_dart.py +0 -0
  140. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_python.py +0 -0
  141. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_universal_managed.py +0 -0
  142. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detector_universal_systems.py +0 -0
  143. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_detectors_base.py +0 -0
  144. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  145. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_doc_analyzer_python.py +0 -0
  146. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  147. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_graph_analyzer_python_node.py +0 -0
  148. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_graph_schema.py +0 -0
  149. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_hybrid_inference.py +0 -0
  150. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration.py +0 -0
  151. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_dependencies.py +0 -0
  152. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_detection.py +0 -0
  153. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_docs.py +0 -0
  154. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_graph_modules.py +0 -0
  155. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_lqn.py +0 -0
  156. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_metrics.py +0 -0
  157. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_multistack.py +0 -0
  158. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_semantics.py +0 -0
  159. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_integration_universal.py +0 -0
  160. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_java_spring_integration.py +0 -0
  161. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_metrics_analyzer.py +0 -0
  162. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_packaging.py +0 -0
  163. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_phase1_improvements.py +0 -0
  164. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_pipeline_integrity.py +0 -0
  165. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_real_projects.py +0 -0
  166. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_redactor.py +0 -0
  167. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_scanner.py +0 -0
  168. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_schema.py +0 -0
  169. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_schema_normalization.py +0 -0
  170. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_semantic_analyzer_node.py +0 -0
  171. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_semantic_analyzer_python.py +0 -0
  172. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_semantic_import_resolution.py +0 -0
  173. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_semantic_schema.py +0 -0
  174. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_signal_hierarchy.py +0 -0
  175. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_summarizer.py +0 -0
  176. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_telemetry.py +0 -0
  177. {sourcecode-1.1.0 → sourcecode-1.2.0}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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.2.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)
@@ -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)
@@ -1376,6 +1376,12 @@ def main(
1376
1376
  ))
1377
1377
  sm = _replace(sm, pipeline_trace=_trace.build_trace())
1378
1378
 
1379
+ # P3-B: Auto-switch to centrality ranking when DDD layout detected
1380
+ if (rank_by == "relevance"
1381
+ and sm.architecture is not None
1382
+ and sm.architecture.pattern == "ddd"):
1383
+ rank_by = "centrality"
1384
+
1379
1385
  # Contract pipeline — runs for mode=contract|standard|deep|hybrid (skip for raw)
1380
1386
  _is_contract_mode = mode in ("contract", "standard")
1381
1387
  if _is_contract_mode:
@@ -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"]
@@ -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(
@@ -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)
@@ -229,6 +229,29 @@ class MetricsAnalyzer:
229
229
  "null complexity fields are expected, not an error."
230
230
  )
231
231
 
232
+ # P2-C: DDD module metrics — group by module, count files/methods per layer
233
+ _DDD_LAYERS = {"domain", "application", "infrastructure"}
234
+ ddd_files = [r for r in records if "/ddd/" in r.path.replace("\\", "/")]
235
+ if ddd_files:
236
+ module_layer_data: dict[str, dict[str, dict]] = {}
237
+ for fm in ddd_files:
238
+ parts = fm.path.replace("\\", "/").split("/")
239
+ for i, part in enumerate(parts):
240
+ if part in _DDD_LAYERS and i >= 2:
241
+ module = parts[i - 1]
242
+ layer = part
243
+ if module not in module_layer_data:
244
+ module_layer_data[module] = {lyr: {"files": 0, "methods": 0} for lyr in _DDD_LAYERS}
245
+ module_layer_data[module][layer]["files"] += 1
246
+ module_layer_data[module][layer]["methods"] += fm.function_count or 0
247
+ break
248
+ ddd_metrics = [
249
+ {"module": mod, "layers": layers}
250
+ for mod, layers in sorted(module_layer_data.items())
251
+ ]
252
+ else:
253
+ ddd_metrics = []
254
+
232
255
  summary = MetricsSummary(
233
256
  requested=True,
234
257
  file_count=len(records),
@@ -238,6 +261,7 @@ class MetricsAnalyzer:
238
261
  coverage_records=coverage_records,
239
262
  coverage_sources_found=sorted({r.format for r in coverage_records}),
240
263
  limitations=limitations,
264
+ ddd_module_metrics=ddd_metrics,
241
265
  )
242
266
  return records, summary
243
267
 
@@ -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
  # -----------------------------------------------------------------------
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes