sourcecode 1.2.0__tar.gz → 1.3.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.2.0 → sourcecode-1.3.0}/PKG-INFO +1 -1
  2. {sourcecode-1.2.0 → sourcecode-1.3.0}/pyproject.toml +1 -1
  3. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/__init__.py +1 -1
  4. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/ast_extractor.py +89 -0
  5. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/cli.py +22 -3
  6. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/contract_pipeline.py +10 -2
  7. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/dependency_analyzer.py +16 -0
  8. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/doc_analyzer.py +76 -1
  9. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/entrypoint_classifier.py +3 -0
  10. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/graph_analyzer.py +1 -1
  11. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/metrics_analyzer.py +16 -1
  12. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/prepare_context.py +27 -0
  13. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/serializer.py +11 -3
  14. {sourcecode-1.2.0 → sourcecode-1.3.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  15. {sourcecode-1.2.0 → sourcecode-1.3.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  16. {sourcecode-1.2.0 → sourcecode-1.3.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  17. {sourcecode-1.2.0 → sourcecode-1.3.0}/.github/workflows/build-windows.yml +0 -0
  18. {sourcecode-1.2.0 → sourcecode-1.3.0}/.gitignore +0 -0
  19. {sourcecode-1.2.0 → sourcecode-1.3.0}/.ruff.toml +0 -0
  20. {sourcecode-1.2.0 → sourcecode-1.3.0}/CONTRIBUTING.md +0 -0
  21. {sourcecode-1.2.0 → sourcecode-1.3.0}/LICENSE +0 -0
  22. {sourcecode-1.2.0 → sourcecode-1.3.0}/README.md +0 -0
  23. {sourcecode-1.2.0 → sourcecode-1.3.0}/SECURITY.md +0 -0
  24. {sourcecode-1.2.0 → sourcecode-1.3.0}/docs/privacy.md +0 -0
  25. {sourcecode-1.2.0 → sourcecode-1.3.0}/docs/schema.md +0 -0
  26. {sourcecode-1.2.0 → sourcecode-1.3.0}/raw +0 -0
  27. {sourcecode-1.2.0 → sourcecode-1.3.0}/run_cli.py +0 -0
  28. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/adaptive_scanner.py +0 -0
  29. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/architecture_analyzer.py +0 -0
  30. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/architecture_summary.py +0 -0
  31. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/classifier.py +0 -0
  32. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  33. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/confidence_analyzer.py +0 -0
  34. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/context_scorer.py +0 -0
  35. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/context_summarizer.py +0 -0
  36. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/contract_model.py +0 -0
  37. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/coverage_parser.py +0 -0
  38. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/__init__.py +0 -0
  39. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/base.py +0 -0
  40. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  41. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/dart.py +0 -0
  42. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/dotnet.py +0 -0
  43. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/elixir.py +0 -0
  44. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/go.py +0 -0
  45. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/heuristic.py +0 -0
  46. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/hybrid.py +0 -0
  47. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/java.py +0 -0
  48. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  49. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/nodejs.py +0 -0
  50. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/parsers.py +0 -0
  51. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/php.py +0 -0
  52. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/project.py +0 -0
  53. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/python.py +0 -0
  54. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/ruby.py +0 -0
  55. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/rust.py +0 -0
  56. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/systems.py +0 -0
  57. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/terraform.py +0 -0
  58. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/detectors/tooling.py +0 -0
  59. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/env_analyzer.py +0 -0
  60. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/git_analyzer.py +0 -0
  62. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/ranking_engine.py +0 -0
  63. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/redactor.py +0 -0
  64. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/relevance_scorer.py +0 -0
  65. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/repo_classifier.py +0 -0
  66. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/runtime_classifier.py +0 -0
  67. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/scanner.py +0 -0
  68. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/schema.py +0 -0
  69. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/semantic_analyzer.py +0 -0
  70. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/summarizer.py +0 -0
  71. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/__init__.py +0 -0
  72. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/config.py +0 -0
  73. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/consent.py +0 -0
  74. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/events.py +0 -0
  75. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/filters.py +0 -0
  76. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/telemetry/transport.py +0 -0
  77. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/tree_utils.py +0 -0
  78. {sourcecode-1.2.0 → sourcecode-1.3.0}/src/sourcecode/workspace.py +0 -0
  79. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/__init__.py +0 -0
  80. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/conftest.py +0 -0
  81. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/coverage.xml +0 -0
  82. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  83. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  84. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  85. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/go_service/go.mod +0 -0
  86. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/jacoco.xml +0 -0
  87. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/lcov.info +0 -0
  88. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  89. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/nextjs_app/package.json +0 -0
  90. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  91. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  92. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  93. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  94. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  95. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  96. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  97. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  98. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  99. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  100. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  101. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  102. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  103. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  104. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  105. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  106. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  107. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  108. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  109. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  110. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  111. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  112. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  113. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  114. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  115. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  116. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  117. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  118. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  119. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  120. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_architecture_analyzer.py +0 -0
  121. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_architecture_summary.py +0 -0
  122. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_ast_extractor.py +0 -0
  123. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_block1_reliability.py +0 -0
  124. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_block2_coverage.py +0 -0
  125. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_block5_quality.py +0 -0
  126. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_classifier.py +0 -0
  127. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_cli.py +0 -0
  128. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_code_notes_analyzer.py +0 -0
  129. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_context_scorer.py +0 -0
  130. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_contract_pipeline.py +0 -0
  131. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_coverage_parser.py +0 -0
  132. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_cross_consistency.py +0 -0
  133. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  134. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  135. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_dependency_schema.py +0 -0
  136. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_dotnet.py +0 -0
  137. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_go_rust_java.py +0 -0
  138. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_nodejs.py +0 -0
  139. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_php_ruby_dart.py +0 -0
  140. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_python.py +0 -0
  141. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_universal_managed.py +0 -0
  142. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detector_universal_systems.py +0 -0
  143. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_detectors_base.py +0 -0
  144. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  145. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_doc_analyzer_python.py +0 -0
  146. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  147. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_graph_analyzer_python_node.py +0 -0
  148. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_graph_schema.py +0 -0
  149. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_hybrid_inference.py +0 -0
  150. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration.py +0 -0
  151. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_dependencies.py +0 -0
  152. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_detection.py +0 -0
  153. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_docs.py +0 -0
  154. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_graph_modules.py +0 -0
  155. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_lqn.py +0 -0
  156. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_metrics.py +0 -0
  157. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_multistack.py +0 -0
  158. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_semantics.py +0 -0
  159. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_integration_universal.py +0 -0
  160. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_java_spring_integration.py +0 -0
  161. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_metrics_analyzer.py +0 -0
  162. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_packaging.py +0 -0
  163. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_phase1_improvements.py +0 -0
  164. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_pipeline_integrity.py +0 -0
  165. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_real_projects.py +0 -0
  166. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_redactor.py +0 -0
  167. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_scanner.py +0 -0
  168. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_schema.py +0 -0
  169. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_schema_normalization.py +0 -0
  170. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_semantic_analyzer_node.py +0 -0
  171. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_semantic_analyzer_python.py +0 -0
  172. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_semantic_import_resolution.py +0 -0
  173. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_semantic_schema.py +0 -0
  174. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_signal_hierarchy.py +0 -0
  175. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_summarizer.py +0 -0
  176. {sourcecode-1.2.0 → sourcecode-1.3.0}/tests/test_telemetry.py +0 -0
  177. {sourcecode-1.2.0 → sourcecode-1.3.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.2.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.2.0"
7
+ version = "1.3.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.2.0"
3
+ __version__ = "1.3.0"
@@ -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)
@@ -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)
@@ -1384,6 +1398,7 @@ def main(
1384
1398
 
1385
1399
  # Contract pipeline — runs for mode=contract|standard|deep|hybrid (skip for raw)
1386
1400
  _is_contract_mode = mode in ("contract", "standard")
1401
+ _pipeline_error = False
1387
1402
  if _is_contract_mode:
1388
1403
  from sourcecode.contract_pipeline import ContractPipeline
1389
1404
  from sourcecode.contract_model import ContractSummary as _ContractSummary
@@ -1408,6 +1423,7 @@ def main(
1408
1423
  )
1409
1424
  except Exception as _exc:
1410
1425
  typer.echo(f"[error] contract pipeline failed: {_exc}", err=True)
1426
+ _pipeline_error = True
1411
1427
  _contracts = []
1412
1428
  _contract_summary = _ContractSummary(
1413
1429
  mode=mode,
@@ -1518,6 +1534,9 @@ def main(
1518
1534
  # 6. Write output (CLI-04)
1519
1535
  write_output(content, output=output)
1520
1536
 
1537
+ if _pipeline_error:
1538
+ raise typer.Exit(code=2)
1539
+
1521
1540
  # 7. Clipboard copy (--copy / -c)
1522
1541
  if copy and output is None:
1523
1542
  _trimmed = content.strip()
@@ -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
 
@@ -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"
@@ -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
@@ -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
@@ -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")
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