sourcecode 1.31.2__tar.gz → 1.31.4__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 (212) hide show
  1. {sourcecode-1.31.2 → sourcecode-1.31.4}/PKG-INFO +3 -3
  2. {sourcecode-1.31.2 → sourcecode-1.31.4}/README.md +2 -2
  3. {sourcecode-1.31.2 → sourcecode-1.31.4}/pyproject.toml +1 -1
  4. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/cli.py +96 -34
  6. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/prepare_context.py +67 -4
  7. sourcecode-1.31.4/tests/test_bug_fixes_v1312.py +239 -0
  8. sourcecode-1.31.4/tests/test_bug_fixes_v1313.py +350 -0
  9. {sourcecode-1.31.2 → sourcecode-1.31.4}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  10. {sourcecode-1.31.2 → sourcecode-1.31.4}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  11. {sourcecode-1.31.2 → sourcecode-1.31.4}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  12. {sourcecode-1.31.2 → sourcecode-1.31.4}/.continue-here.md +0 -0
  13. {sourcecode-1.31.2 → sourcecode-1.31.4}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-1.31.2 → sourcecode-1.31.4}/.gitignore +0 -0
  15. {sourcecode-1.31.2 → sourcecode-1.31.4}/.ruff.toml +0 -0
  16. {sourcecode-1.31.2 → sourcecode-1.31.4}/CHANGELOG.md +0 -0
  17. {sourcecode-1.31.2 → sourcecode-1.31.4}/CONTRIBUTING.md +0 -0
  18. {sourcecode-1.31.2 → sourcecode-1.31.4}/LICENSE +0 -0
  19. {sourcecode-1.31.2 → sourcecode-1.31.4}/SECURITY.md +0 -0
  20. {sourcecode-1.31.2 → sourcecode-1.31.4}/docs/privacy.md +0 -0
  21. {sourcecode-1.31.2 → sourcecode-1.31.4}/docs/schema.md +0 -0
  22. {sourcecode-1.31.2 → sourcecode-1.31.4}/raw +0 -0
  23. {sourcecode-1.31.2 → sourcecode-1.31.4}/run_cli.py +0 -0
  24. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/adaptive_scanner.py +0 -0
  25. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/architecture_analyzer.py +0 -0
  26. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/architecture_summary.py +0 -0
  27. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/ast_extractor.py +0 -0
  28. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/classifier.py +0 -0
  29. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/code_notes_analyzer.py +0 -0
  30. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/confidence_analyzer.py +0 -0
  31. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/context_scorer.py +0 -0
  32. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/context_summarizer.py +0 -0
  33. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/contract_model.py +0 -0
  34. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/contract_pipeline.py +0 -0
  35. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/coverage_parser.py +0 -0
  36. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/dependency_analyzer.py +0 -0
  37. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/__init__.py +0 -0
  38. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/base.py +0 -0
  39. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/csproj_parser.py +0 -0
  40. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/dart.py +0 -0
  41. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/dotnet.py +0 -0
  42. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/elixir.py +0 -0
  43. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/go.py +0 -0
  44. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/heuristic.py +0 -0
  45. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/hybrid.py +0 -0
  46. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/java.py +0 -0
  47. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/jvm_ext.py +0 -0
  48. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/nodejs.py +0 -0
  49. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/parsers.py +0 -0
  50. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/php.py +0 -0
  51. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/project.py +0 -0
  52. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/python.py +0 -0
  53. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/ruby.py +0 -0
  54. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/rust.py +0 -0
  55. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/systems.py +0 -0
  56. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/terraform.py +0 -0
  57. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/detectors/tooling.py +0 -0
  58. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/doc_analyzer.py +0 -0
  59. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/entrypoint_classifier.py +0 -0
  60. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/env_analyzer.py +0 -0
  61. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/file_classifier.py +0 -0
  62. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/flow_analyzer.py +0 -0
  63. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/git_analyzer.py +0 -0
  64. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/graph_analyzer.py +0 -0
  65. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/__init__.py +0 -0
  66. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  67. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  68. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  69. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  70. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  71. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/runner.py +0 -0
  72. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/mcp/server.py +0 -0
  73. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/metrics_analyzer.py +0 -0
  74. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/pr_comment_renderer.py +0 -0
  75. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/progress.py +0 -0
  76. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/ranking_engine.py +0 -0
  77. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/redactor.py +0 -0
  78. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/relevance_scorer.py +0 -0
  79. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/repo_classifier.py +0 -0
  80. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/repository_ir.py +0 -0
  81. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/runtime_classifier.py +0 -0
  82. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/scanner.py +0 -0
  83. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/schema.py +0 -0
  84. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/semantic_analyzer.py +0 -0
  85. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/serializer.py +0 -0
  86. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/summarizer.py +0 -0
  87. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/__init__.py +0 -0
  88. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/config.py +0 -0
  89. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/consent.py +0 -0
  90. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/events.py +0 -0
  91. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/filters.py +0 -0
  92. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/telemetry/transport.py +0 -0
  93. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/tree_utils.py +0 -0
  94. {sourcecode-1.31.2 → sourcecode-1.31.4}/src/sourcecode/workspace.py +0 -0
  95. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/__init__.py +0 -0
  96. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/conftest.py +0 -0
  97. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/coverage.xml +0 -0
  98. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  99. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/fastapi_app/src/main.py +0 -0
  100. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  101. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/go_service/go.mod +0 -0
  102. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/jacoco.xml +0 -0
  103. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/latin1_sample.java +0 -0
  104. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/latin1_sample_iso.java +0 -0
  105. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/lcov.info +0 -0
  106. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  107. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/nextjs_app/package.json +0 -0
  108. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  109. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  110. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  111. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  112. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  113. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  114. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  115. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  116. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  117. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  118. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  119. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  120. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  121. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  122. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  123. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  124. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  125. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  126. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  127. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  128. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  129. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  130. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  131. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  132. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  133. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  134. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  135. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  136. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  137. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  138. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  139. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  140. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  141. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_architecture_analyzer.py +0 -0
  142. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_architecture_summary.py +0 -0
  143. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_ast_extractor.py +0 -0
  144. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_block1_reliability.py +0 -0
  145. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_block2_coverage.py +0 -0
  146. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_block5_quality.py +0 -0
  147. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_bug_fixes_v1302.py +0 -0
  148. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_bug_fixes_v16.py +0 -0
  149. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_bug_fixes_v2.py +0 -0
  150. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_classifier.py +0 -0
  151. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_cli.py +0 -0
  152. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_code_notes_analyzer.py +0 -0
  153. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_context_scorer.py +0 -0
  154. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_contract_pipeline.py +0 -0
  155. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_coverage_parser.py +0 -0
  156. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_cross_consistency.py +0 -0
  157. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_dependency_analyzer_node_python.py +0 -0
  158. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_dependency_analyzer_polyglot.py +0 -0
  159. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_dependency_schema.py +0 -0
  160. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_dotnet.py +0 -0
  161. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_go_rust_java.py +0 -0
  162. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_nodejs.py +0 -0
  163. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_php_ruby_dart.py +0 -0
  164. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_python.py +0 -0
  165. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_universal_managed.py +0 -0
  166. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detector_universal_systems.py +0 -0
  167. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_detectors_base.py +0 -0
  168. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_doc_analyzer_jsdom.py +0 -0
  169. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_doc_analyzer_python.py +0 -0
  170. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_encoding_regression.py +0 -0
  171. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_graph_analyzer_polyglot.py +0 -0
  172. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_graph_analyzer_python_node.py +0 -0
  173. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_graph_schema.py +0 -0
  174. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_hybrid_inference.py +0 -0
  175. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration.py +0 -0
  176. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_dependencies.py +0 -0
  177. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_detection.py +0 -0
  178. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_docs.py +0 -0
  179. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_graph_modules.py +0 -0
  180. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_lqn.py +0 -0
  181. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_metrics.py +0 -0
  182. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_multistack.py +0 -0
  183. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_semantics.py +0 -0
  184. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_integration_universal.py +0 -0
  185. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_java_spring_integration.py +0 -0
  186. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_mcp_runner.py +0 -0
  187. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_mcp_serve.py +0 -0
  188. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_mcp_tools.py +0 -0
  189. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_metrics_analyzer.py +0 -0
  190. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_output_ux.py +0 -0
  191. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_packaging.py +0 -0
  192. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_phase1_improvements.py +0 -0
  193. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_pipeline_integrity.py +0 -0
  194. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_real_projects.py +0 -0
  195. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_redactor.py +0 -0
  196. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_repository_ir.py +0 -0
  197. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_scanner.py +0 -0
  198. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_schema.py +0 -0
  199. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_schema_normalization.py +0 -0
  200. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_scoring_calibration.py +0 -0
  201. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_semantic_analyzer_node.py +0 -0
  202. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_semantic_analyzer_python.py +0 -0
  203. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_semantic_import_resolution.py +0 -0
  204. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_semantic_schema.py +0 -0
  205. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_signal_hierarchy.py +0 -0
  206. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_summarizer.py +0 -0
  207. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_surface_honesty.py +0 -0
  208. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_task_differentiation.py +0 -0
  209. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_telemetry.py +0 -0
  210. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_v131_improvements.py +0 -0
  211. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_v1_10_regressions.py +0 -0
  212. {sourcecode-1.31.2 → sourcecode-1.31.4}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.31.2
3
+ Version: 1.31.4
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
225
225
 
226
226
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
227
227
 
228
- ![Version](https://img.shields.io/badge/version-1.31.2-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.4-blue)
229
229
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
230
230
 
231
231
  ---
@@ -261,7 +261,7 @@ pipx install sourcecode
261
261
 
262
262
  ```bash
263
263
  sourcecode version
264
- # sourcecode 1.31.2
264
+ # sourcecode 1.31.4
265
265
  ```
266
266
 
267
267
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.31.2-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.31.4-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -38,7 +38,7 @@ pipx install sourcecode
38
38
 
39
39
  ```bash
40
40
  sourcecode version
41
- # sourcecode 1.31.2
41
+ # sourcecode 1.31.4
42
42
  ```
43
43
 
44
44
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.31.2"
7
+ version = "1.31.4"
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.31.2"
3
+ __version__ = "1.31.4"
@@ -1849,6 +1849,11 @@ def prepare_context_cmd(
1849
1849
  "--include-config",
1850
1850
  help="(generate-tests) Include tooling config files (*.conf.js, .eslintrc*, etc.) in test_gaps. Excluded by default.",
1851
1851
  ),
1852
+ all_gaps: bool = typer.Option(
1853
+ False,
1854
+ "--all",
1855
+ help="(generate-tests) Return the full test_gaps list without truncating to top 20.",
1856
+ ),
1852
1857
  ) -> None:
1853
1858
  """Task-specific context for AI coding agents.
1854
1859
 
@@ -1930,7 +1935,7 @@ def prepare_context_cmd(
1930
1935
  _sys.stderr.flush()
1931
1936
  _t0 = _time.perf_counter()
1932
1937
  try:
1933
- output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config)
1938
+ output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config, all_gaps=all_gaps)
1934
1939
  finally:
1935
1940
  _progress.finish()
1936
1941
  _t_total = (_time.perf_counter() - _t0) * 1000
@@ -1957,8 +1962,8 @@ def prepare_context_cmd(
1957
1962
  },
1958
1963
  "explain": {
1959
1964
  "project_summary": True, "architecture_summary": True,
1960
- "relevant_files": True, "key_dependencies": True,
1961
- "gaps": True, "confidence": True,
1965
+ "relevant_files": False, "key_dependencies": True,
1966
+ "gaps": False, "confidence": True,
1962
1967
  "suspected_areas": False, "improvement_opportunities": False,
1963
1968
  "test_gaps": False, "code_notes_summary": False,
1964
1969
  "changed_files": False, "affected_entry_points": False,
@@ -2467,6 +2472,11 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2467
2472
  _CLASS_PATH_RE = _re.compile(
2468
2473
  r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']',
2469
2474
  )
2475
+ # Handles array syntax: @RequestMapping({"/v1/foo", "/v1/bar"})
2476
+ _CLASS_ARRAY_PATH_RE = _re.compile(r'@RequestMapping\s*\(\s*\{([^}]*)\}')
2477
+ _QUOTED_STR_RE = _re.compile(r'"([^"]+)"')
2478
+ _REQUEST_METHOD_VERB_RE = _re.compile(r'method\s*=\s*RequestMethod\.([A-Z]+)')
2479
+ _VALUE_PATH_RE = _re.compile(r'value\s*=\s*"([^"]+)"')
2470
2480
 
2471
2481
  _HTTP_METHOD_MAP = {
2472
2482
  "Get": "GET", "Post": "POST", "Put": "PUT",
@@ -2502,27 +2512,66 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2502
2512
  cls_m = _CLASS_RE.search(content)
2503
2513
  class_name = cls_m.group(1) if cls_m else java_file.stem
2504
2514
 
2505
- # Extract class-level base path from @RequestMapping on the class
2506
- class_base = ""
2515
+ # Extract class-level base path and locate class body start
2507
2516
  lines = content.splitlines()
2517
+
2518
+ # First pass: find class/interface declaration line index
2519
+ class_body_start = 0
2508
2520
  for i, line in enumerate(lines):
2509
- if "@RequestMapping" in line and i < len(lines) - 1:
2510
- # Check if next non-blank line is class declaration or it's on same block
2511
- block = "\n".join(lines[max(0, i - 1): i + 5])
2521
+ stripped_l = line.strip()
2522
+ if (not stripped_l.startswith("//") and not stripped_l.startswith("*")
2523
+ and ("class " in stripped_l or "interface " in stripped_l)
2524
+ and _CLASS_RE.search(stripped_l)):
2525
+ class_body_start = i + 1
2526
+ break
2527
+
2528
+ # Second pass: extract class-level @RequestMapping path (only before class body).
2529
+ # Supports both single-string and array syntax (Bug #1B).
2530
+ search_end = class_body_start if class_body_start else len(lines)
2531
+ class_bases: list[str] = [""] # default: no prefix
2532
+ for i in range(search_end):
2533
+ sl = lines[i].strip()
2534
+ if sl.startswith("//") or sl.startswith("*"):
2535
+ continue
2536
+ if "@RequestMapping" in lines[i]:
2537
+ # Cap block to search_end so method annotations inside the class body
2538
+ # cannot be matched as the class-level prefix (Bug #1B).
2539
+ block = "\n".join(lines[max(0, i - 1): min(i + 5, search_end + 1)])
2512
2540
  if "class " in block or "interface " in block:
2513
2541
  path_m = _CLASS_PATH_RE.search(block)
2514
2542
  if path_m:
2515
- class_base = path_m.group(1).rstrip("/")
2516
- break
2517
-
2518
- # Extract method-level endpoints
2519
- # Parse line-by-line to associate annotations with methods
2543
+ class_bases = [path_m.group(1).rstrip("/")]
2544
+ else:
2545
+ arr_m = _CLASS_ARRAY_PATH_RE.search(block)
2546
+ if arr_m:
2547
+ paths = _QUOTED_STR_RE.findall(arr_m.group(1))
2548
+ if paths:
2549
+ class_bases = [p.rstrip("/") for p in paths]
2550
+ break
2551
+
2552
+ # Extract method-level endpoints starting from inside class body.
2553
+ # Bug #1A fix: track block comments and skip // lines before annotation checks.
2520
2554
  pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
2521
2555
  pending_filtro: Optional[str] = None
2556
+ in_block_comment = False
2522
2557
 
2523
- for i, line in enumerate(lines):
2558
+ for i in range(class_body_start, len(lines)):
2559
+ line = lines[i]
2524
2560
  stripped = line.strip()
2525
2561
 
2562
+ # Block comment state tracking (Bug #1A)
2563
+ if in_block_comment:
2564
+ if "*/" in stripped:
2565
+ in_block_comment = False
2566
+ continue
2567
+ if stripped.startswith("/*"):
2568
+ if "*/" not in stripped:
2569
+ in_block_comment = True
2570
+ continue
2571
+ # Skip line comments (Bug #1A)
2572
+ if stripped.startswith("//"):
2573
+ continue
2574
+
2526
2575
  # Check for @M3FiltroSeguridad
2527
2576
  fm = _FILTRO_RE.search(stripped)
2528
2577
  if fm:
@@ -2535,6 +2584,19 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2535
2584
  verb_key = hm.group(1)
2536
2585
  http_verb = _HTTP_METHOD_MAP.get(verb_key, "GET")
2537
2586
  path_suffix = (hm.group(2) or "").strip()
2587
+
2588
+ # For @RequestMapping: resolve HTTP method from method= attribute
2589
+ if verb_key == "Request":
2590
+ annotation_block = "\n".join(lines[i:min(i + 5, len(lines))])
2591
+ vm = _REQUEST_METHOD_VERB_RE.search(annotation_block)
2592
+ if vm:
2593
+ http_verb = vm.group(1)
2594
+ # If path not captured (method= precedes value=), try value= extraction
2595
+ if not path_suffix:
2596
+ vp = _VALUE_PATH_RE.search(annotation_block)
2597
+ if vp:
2598
+ path_suffix = vp.group(1)
2599
+
2538
2600
  pending_annotations.append((http_verb, path_suffix))
2539
2601
  continue
2540
2602
 
@@ -2544,30 +2606,30 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2544
2606
  handler = mm.group(1) if mm else ""
2545
2607
  if handler and not handler.startswith("class"):
2546
2608
  for http_verb, path_suffix in pending_annotations:
2547
- full_path = (class_base + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
2548
- if not full_path.startswith("/"):
2549
- full_path = "/" + full_path
2550
- key = (class_name, handler, http_verb)
2551
- if key not in seen:
2552
- seen.add(key)
2553
- entry: dict[str, Any] = {
2554
- "method": http_verb,
2555
- "path": full_path,
2556
- "controller": class_name,
2557
- "handler": handler,
2558
- }
2559
- if pending_filtro:
2560
- entry["required_permission"] = pending_filtro
2561
- endpoints.append(entry)
2609
+ for _cb in class_bases: # Bug #1B: one endpoint per class prefix
2610
+ full_path = (_cb + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
2611
+ if not full_path.startswith("/"):
2612
+ full_path = "/" + full_path
2613
+ key = (class_name, handler, http_verb, _cb)
2614
+ if key not in seen:
2615
+ seen.add(key)
2616
+ entry: dict[str, Any] = {
2617
+ "method": http_verb,
2618
+ "path": full_path,
2619
+ "controller": class_name,
2620
+ "handler": handler,
2621
+ }
2622
+ if pending_filtro:
2623
+ entry["required_permission"] = pending_filtro
2624
+ endpoints.append(entry)
2562
2625
  pending_annotations = []
2563
2626
  pending_filtro = None
2564
2627
  continue
2565
2628
 
2566
- # Non-annotation, non-method line — reset if it's a closing brace or blank
2567
- if stripped in ("}", "{", "") or stripped.startswith("//") or stripped.startswith("*"):
2568
- if stripped == "}":
2569
- pending_annotations = []
2570
- pending_filtro = None
2629
+ # Non-annotation, non-method line — reset on closing brace
2630
+ if stripped == "}":
2631
+ pending_annotations = []
2632
+ pending_filtro = None
2571
2633
 
2572
2634
  endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
2573
2635
  undocumented = sum(1 for e in endpoints if "required_permission" not in e)
@@ -721,7 +721,7 @@ class TaskContextBuilder:
721
721
  def __init__(self, root: Path) -> None:
722
722
  self.root = root
723
723
 
724
- def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False, include_config: bool = False) -> TaskOutput:
724
+ def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False, include_config: bool = False, all_gaps: bool = False) -> TaskOutput:
725
725
  if task_name not in TASKS:
726
726
  raise ValueError(
727
727
  f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
@@ -1763,14 +1763,18 @@ class TaskContextBuilder:
1763
1763
  "_rank": _pub_count + _ann_count * 2,
1764
1764
  })
1765
1765
 
1766
- _java_candidates.sort(key=lambda x: -x["_rank"])
1766
+ _java_candidates.sort(
1767
+ key=lambda x: -(x["public_method_count"] * (1.5 if x["has_spring_annotations"] else 1.0))
1768
+ )
1769
+ _top = _java_candidates if all_gaps else _java_candidates[:20]
1767
1770
  test_gaps = [
1768
1771
  {
1769
1772
  "path": c["path"],
1770
1773
  "public_method_count": c["public_method_count"],
1771
1774
  "has_spring_annotations": c["has_spring_annotations"],
1775
+ "rank_score": round(c["public_method_count"] * (1.5 if c["has_spring_annotations"] else 1.0), 1),
1772
1776
  }
1773
- for c in _java_candidates
1777
+ for c in _top
1774
1778
  ]
1775
1779
  else:
1776
1780
  # Non-Java algorithm (unchanged)
@@ -2206,8 +2210,67 @@ class TaskContextBuilder:
2206
2210
  "refactor": max(15, min(30, _repo_size // 120)),
2207
2211
  }
2208
2212
  _budget = _task_budget.get(task_name, 15)
2209
- _selected = _ctx.select_subgraph(_ns, contracts=[], budget=_budget, min_score=0.15)
2213
+ _selected = list(_ctx.select_subgraph(_ns, contracts=[], budget=_budget, min_score=0.15))
2210
2214
  _rf_map = {path: rf for _, path, rf in scored}
2215
+
2216
+ # Bug #3: onboard must cover ≥3 arch layers (controllers/services/domain/repositories).
2217
+ if task_name == "onboard":
2218
+ def _arch_layer(p: str) -> str:
2219
+ n = Path(p).name.lower()
2220
+ if "controller" in n:
2221
+ return "controllers"
2222
+ if "repository" in n or "mapper" in n or "dao" in n:
2223
+ return "repositories"
2224
+ if "service" in n:
2225
+ return "services"
2226
+ pn = p.replace("\\", "/")
2227
+ if "entity" in n or "/entity/" in pn or "/domain/" in pn or "/model/" in pn:
2228
+ return "domain"
2229
+ return "other"
2230
+
2231
+ _REQUIRED = {"controllers", "services", "repositories", "domain"}
2232
+ _covered = {_arch_layer(p) for p in _selected} & _REQUIRED
2233
+ _missing = _REQUIRED - _covered
2234
+ if len(_covered) < 3 and _missing:
2235
+ _sel_set = set(_selected)
2236
+ # First pass: inject from already-scored files
2237
+ for _, _p, _ in sorted(scored, key=lambda x: -x[0]):
2238
+ if len(_covered) >= 3:
2239
+ break
2240
+ if _p in _sel_set or _p not in _rf_map:
2241
+ continue
2242
+ _layer = _arch_layer(_p)
2243
+ if _layer in _missing:
2244
+ _selected.append(_p)
2245
+ _sel_set.add(_p)
2246
+ _covered.add(_layer)
2247
+ _missing.discard(_layer)
2248
+ # Second pass: fallback scan of all_paths when scored files
2249
+ # don't cover enough layers (e.g. all Java files scored ≤ 0
2250
+ # due to auxiliary/example package detection).
2251
+ if len(_covered) < 3 and _missing:
2252
+ _NON_TEST = ("/test/", "/tests/", "/spec/")
2253
+ for _p in all_paths:
2254
+ if len(_covered) >= 3:
2255
+ break
2256
+ if _p in _sel_set:
2257
+ continue
2258
+ if any(s in _p.replace("\\", "/") for s in _NON_TEST):
2259
+ continue
2260
+ _layer = _arch_layer(_p)
2261
+ if _layer not in _missing:
2262
+ continue
2263
+ _rf_map[_p] = RelevantFile(
2264
+ path=_p,
2265
+ role="source",
2266
+ score=0.1,
2267
+ reason="layer coverage (onboard)",
2268
+ )
2269
+ _selected.append(_p)
2270
+ _sel_set.add(_p)
2271
+ _covered.add(_layer)
2272
+ _missing.discard(_layer)
2273
+
2211
2274
  return [_rf_map[p] for p in _selected if p in _rf_map]
2212
2275
  except Exception:
2213
2276
  return [f for _, _, f in scored[:15]]
@@ -0,0 +1,239 @@
1
+ """Tests for v1.31.2 bug fixes.
2
+
3
+ BUG #1 endpoints: class-level @RequestMapping leaked into method loop
4
+ → all methods showed class path only (or double prefix)
5
+ BUG #2 endpoints: @RequestMapping(method=RequestMethod.DELETE) resolved as GET
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import textwrap
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+ from typer.testing import CliRunner
15
+
16
+ from sourcecode.cli import app, _extract_java_endpoints
17
+
18
+ runner = CliRunner()
19
+
20
+
21
+ # ── helpers ───────────────────────────────────────────────────────────────────
22
+
23
+ def _write_java(tmp_path: Path, filename: str, source: str) -> Path:
24
+ f = tmp_path / filename
25
+ f.write_text(textwrap.dedent(source), encoding="utf-8")
26
+ return tmp_path
27
+
28
+
29
+ # ── BUG #1: path concatenation ─────────────────────────────────────────────
30
+
31
+
32
+ def test_acceso_rol_paths_correct(tmp_path):
33
+ """AccesoRolRestController: each method gets class_base + method_path."""
34
+ _write_java(tmp_path, "AccesoRolRestController.java", """\
35
+ @RestController
36
+ @RequestMapping("/v1/accesoRol")
37
+ public class AccesoRolRestController {
38
+
39
+ @M3FiltroSeguridad("accesoRol")
40
+ @GetMapping("/list")
41
+ public ResponseEntity<?> list() { return null; }
42
+
43
+ @PostMapping("/create")
44
+ public ResponseEntity<?> create() { return null; }
45
+
46
+ @DeleteMapping("/{id}")
47
+ public ResponseEntity<?> delete() { return null; }
48
+ }
49
+ """)
50
+ result = _extract_java_endpoints(tmp_path)
51
+ paths = {e["path"] for e in result["endpoints"]}
52
+ assert "/v1/accesoRol/list" in paths, f"expected /v1/accesoRol/list, got {paths}"
53
+ assert "/v1/accesoRol/create" in paths
54
+ assert "/v1/accesoRol/{id}" in paths
55
+ # class path alone must NOT appear as a standalone endpoint
56
+ assert "/v1/accesoRol" not in paths
57
+ # no double-prefix
58
+ assert not any("/v1/accesoRol/v1/accesoRol" in p for p in paths)
59
+
60
+
61
+ def test_no_double_prefix(tmp_path):
62
+ """Class path must not be concatenated with itself."""
63
+ _write_java(tmp_path, "PortalController.java", """\
64
+ @RestController
65
+ @RequestMapping("/v1/portal/acreditaciones")
66
+ public class PortalController {
67
+
68
+ @GetMapping("/all")
69
+ public List<?> getAll() { return null; }
70
+ }
71
+ """)
72
+ result = _extract_java_endpoints(tmp_path)
73
+ paths = {e["path"] for e in result["endpoints"]}
74
+ assert "/v1/portal/acreditaciones/all" in paths
75
+ assert not any("acreditaciones/v1" in p for p in paths)
76
+
77
+
78
+ def test_method_count_one_per_handler(tmp_path):
79
+ """Each handler must produce exactly one endpoint, not two."""
80
+ _write_java(tmp_path, "FooController.java", """\
81
+ @RestController
82
+ @RequestMapping("/api")
83
+ public class FooController {
84
+
85
+ @GetMapping("/items")
86
+ public List<?> items() { return null; }
87
+
88
+ @PostMapping("/items")
89
+ public void save() { }
90
+ }
91
+ """)
92
+ result = _extract_java_endpoints(tmp_path)
93
+ assert result["total"] == 2
94
+ handlers = [e["handler"] for e in result["endpoints"]]
95
+ assert len(handlers) == len(set(handlers)), "duplicate handlers found"
96
+
97
+
98
+ def test_no_class_level_path_in_method_loop(tmp_path):
99
+ """Class-level @RequestMapping must not pollute method loop."""
100
+ _write_java(tmp_path, "SimpleController.java", """\
101
+ @RestController
102
+ @RequestMapping(value = "/base")
103
+ public class SimpleController {
104
+
105
+ @GetMapping
106
+ public String root() { return "ok"; }
107
+ }
108
+ """)
109
+ result = _extract_java_endpoints(tmp_path)
110
+ # root() with @GetMapping (no path) → /base
111
+ assert result["total"] == 1
112
+ assert result["endpoints"][0]["path"] == "/base"
113
+ assert result["endpoints"][0]["handler"] == "root"
114
+
115
+
116
+ # ── BUG #2: HTTP method resolution ────────────────────────────────────────────
117
+
118
+
119
+ def test_request_mapping_delete_method(tmp_path):
120
+ """@RequestMapping(method=RequestMethod.DELETE) must resolve to DELETE."""
121
+ _write_java(tmp_path, "RolController.java", """\
122
+ @RestController
123
+ @RequestMapping("/v1/rol")
124
+ public class RolController {
125
+
126
+ @RequestMapping(value = "/borrarMultiple", method = RequestMethod.DELETE)
127
+ public ResponseEntity<?> borrarMultiple() { return null; }
128
+ }
129
+ """)
130
+ result = _extract_java_endpoints(tmp_path)
131
+ assert result["total"] == 1
132
+ ep = result["endpoints"][0]
133
+ assert ep["method"] == "DELETE", f"expected DELETE, got {ep['method']}"
134
+ assert ep["handler"] == "borrarMultiple"
135
+ assert ep["path"] == "/v1/rol/borrarMultiple"
136
+
137
+
138
+ def test_request_mapping_post_method(tmp_path):
139
+ """@RequestMapping(method=RequestMethod.POST) must resolve to POST."""
140
+ _write_java(tmp_path, "ItemController.java", """\
141
+ @RestController
142
+ @RequestMapping("/items")
143
+ public class ItemController {
144
+
145
+ @RequestMapping(value = "/create", method = RequestMethod.POST)
146
+ public ResponseEntity<?> create() { return null; }
147
+ }
148
+ """)
149
+ result = _extract_java_endpoints(tmp_path)
150
+ ep = result["endpoints"][0]
151
+ assert ep["method"] == "POST"
152
+
153
+
154
+ def test_request_mapping_no_method_defaults_get(tmp_path):
155
+ """@RequestMapping without method= defaults to GET."""
156
+ _write_java(tmp_path, "InfoController.java", """\
157
+ @RestController
158
+ public class InfoController {
159
+
160
+ @RequestMapping("/health")
161
+ public String health() { return "ok"; }
162
+ }
163
+ """)
164
+ result = _extract_java_endpoints(tmp_path)
165
+ assert result["endpoints"][0]["method"] == "GET"
166
+
167
+
168
+ def test_request_mapping_method_before_value(tmp_path):
169
+ """@RequestMapping(method=DELETE, value="/path") — method= before value=."""
170
+ _write_java(tmp_path, "OrderController.java", """\
171
+ @RestController
172
+ @RequestMapping("/orders")
173
+ public class OrderController {
174
+
175
+ @RequestMapping(method = RequestMethod.DELETE, value = "/cancel")
176
+ public void cancel() { }
177
+ }
178
+ """)
179
+ result = _extract_java_endpoints(tmp_path)
180
+ ep = result["endpoints"][0]
181
+ assert ep["method"] == "DELETE"
182
+ assert ep["path"] == "/orders/cancel"
183
+
184
+
185
+ def test_delete_mapping_annotation(tmp_path):
186
+ """@DeleteMapping must still resolve to DELETE (regression guard)."""
187
+ _write_java(tmp_path, "TaskController.java", """\
188
+ @RestController
189
+ @RequestMapping("/tasks")
190
+ public class TaskController {
191
+
192
+ @DeleteMapping("/{id}")
193
+ public void remove() { }
194
+ }
195
+ """)
196
+ result = _extract_java_endpoints(tmp_path)
197
+ assert result["endpoints"][0]["method"] == "DELETE"
198
+
199
+
200
+ # ── combined: paths + methods ─────────────────────────────────────────────────
201
+
202
+
203
+ def test_mixed_controller(tmp_path):
204
+ """Full CRUD controller: correct paths and methods for all verbs."""
205
+ _write_java(tmp_path, "CrudController.java", """\
206
+ @RestController
207
+ @RequestMapping("/v1/items")
208
+ public class CrudController {
209
+
210
+ @GetMapping
211
+ public List<?> getAll() { return null; }
212
+
213
+ @GetMapping("/{id}")
214
+ public Object getById() { return null; }
215
+
216
+ @PostMapping
217
+ public Object create() { return null; }
218
+
219
+ @PutMapping("/{id}")
220
+ public Object update() { return null; }
221
+
222
+ @DeleteMapping("/{id}")
223
+ public void delete() { }
224
+
225
+ @RequestMapping(value = "/bulk", method = RequestMethod.DELETE)
226
+ public void deleteBulk() { }
227
+ }
228
+ """)
229
+ result = _extract_java_endpoints(tmp_path)
230
+ by_handler = {e["handler"]: e for e in result["endpoints"]}
231
+
232
+ assert by_handler["getAll"]["method"] == "GET"
233
+ assert by_handler["getAll"]["path"] == "/v1/items"
234
+ assert by_handler["getById"]["path"] == "/v1/items/{id}"
235
+ assert by_handler["create"]["method"] == "POST"
236
+ assert by_handler["update"]["method"] == "PUT"
237
+ assert by_handler["delete"]["method"] == "DELETE"
238
+ assert by_handler["deleteBulk"]["method"] == "DELETE"
239
+ assert by_handler["deleteBulk"]["path"] == "/v1/items/bulk"