sourcecode 1.30.26__tar.gz → 1.30.28__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 (197) hide show
  1. {sourcecode-1.30.26 → sourcecode-1.30.28}/PKG-INFO +3 -3
  2. {sourcecode-1.30.26 → sourcecode-1.30.28}/README.md +2 -2
  3. {sourcecode-1.30.26 → sourcecode-1.30.28}/pyproject.toml +1 -1
  4. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/cli.py +19 -5
  6. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/prepare_context.py +30 -3
  7. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/ranking_engine.py +92 -0
  8. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/serializer.py +89 -66
  9. sourcecode-1.30.28/tests/test_bug_fixes_v1302.py +419 -0
  10. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_scoring_calibration.py +15 -11
  11. {sourcecode-1.30.26 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  12. {sourcecode-1.30.26 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  13. {sourcecode-1.30.26 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  14. {sourcecode-1.30.26 → sourcecode-1.30.28}/.continue-here.md +0 -0
  15. {sourcecode-1.30.26 → sourcecode-1.30.28}/.github/workflows/build-windows.yml +0 -0
  16. {sourcecode-1.30.26 → sourcecode-1.30.28}/.gitignore +0 -0
  17. {sourcecode-1.30.26 → sourcecode-1.30.28}/.ruff.toml +0 -0
  18. {sourcecode-1.30.26 → sourcecode-1.30.28}/CONTRIBUTING.md +0 -0
  19. {sourcecode-1.30.26 → sourcecode-1.30.28}/LICENSE +0 -0
  20. {sourcecode-1.30.26 → sourcecode-1.30.28}/SECURITY.md +0 -0
  21. {sourcecode-1.30.26 → sourcecode-1.30.28}/docs/privacy.md +0 -0
  22. {sourcecode-1.30.26 → sourcecode-1.30.28}/docs/schema.md +0 -0
  23. {sourcecode-1.30.26 → sourcecode-1.30.28}/raw +0 -0
  24. {sourcecode-1.30.26 → sourcecode-1.30.28}/run_cli.py +0 -0
  25. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/adaptive_scanner.py +0 -0
  26. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/architecture_analyzer.py +0 -0
  27. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/architecture_summary.py +0 -0
  28. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/ast_extractor.py +0 -0
  29. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/code_notes_analyzer.py +0 -0
  31. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/confidence_analyzer.py +0 -0
  32. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/context_scorer.py +0 -0
  33. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/context_summarizer.py +0 -0
  34. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/contract_model.py +0 -0
  35. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/contract_pipeline.py +0 -0
  36. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/coverage_parser.py +0 -0
  37. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/dependency_analyzer.py +0 -0
  38. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/__init__.py +0 -0
  39. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/base.py +0 -0
  40. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/csproj_parser.py +0 -0
  41. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/dart.py +0 -0
  42. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/dotnet.py +0 -0
  43. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/elixir.py +0 -0
  44. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/go.py +0 -0
  45. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/heuristic.py +0 -0
  46. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/hybrid.py +0 -0
  47. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/java.py +0 -0
  48. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/jvm_ext.py +0 -0
  49. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/nodejs.py +0 -0
  50. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/parsers.py +0 -0
  51. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/php.py +0 -0
  52. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/project.py +0 -0
  53. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/python.py +0 -0
  54. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/ruby.py +0 -0
  55. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/rust.py +0 -0
  56. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/systems.py +0 -0
  57. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/terraform.py +0 -0
  58. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/detectors/tooling.py +0 -0
  59. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/doc_analyzer.py +0 -0
  60. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/entrypoint_classifier.py +0 -0
  61. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/env_analyzer.py +0 -0
  62. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/file_classifier.py +0 -0
  63. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/flow_analyzer.py +0 -0
  64. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/git_analyzer.py +0 -0
  65. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/graph_analyzer.py +0 -0
  66. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/metrics_analyzer.py +0 -0
  67. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/pr_comment_renderer.py +0 -0
  68. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/progress.py +0 -0
  69. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/redactor.py +0 -0
  70. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/relevance_scorer.py +0 -0
  71. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/repo_classifier.py +0 -0
  72. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/repository_ir.py +0 -0
  73. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/runtime_classifier.py +0 -0
  74. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/scanner.py +0 -0
  75. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/schema.py +0 -0
  76. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/semantic_analyzer.py +0 -0
  77. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/summarizer.py +0 -0
  78. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/__init__.py +0 -0
  79. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/config.py +0 -0
  80. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/consent.py +0 -0
  81. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/events.py +0 -0
  82. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/filters.py +0 -0
  83. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/telemetry/transport.py +0 -0
  84. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/tree_utils.py +0 -0
  85. {sourcecode-1.30.26 → sourcecode-1.30.28}/src/sourcecode/workspace.py +0 -0
  86. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/__init__.py +0 -0
  87. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/conftest.py +0 -0
  88. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/coverage.xml +0 -0
  89. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  90. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/fastapi_app/src/main.py +0 -0
  91. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  92. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/go_service/go.mod +0 -0
  93. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/jacoco.xml +0 -0
  94. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/latin1_sample.java +0 -0
  95. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/latin1_sample_iso.java +0 -0
  96. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/lcov.info +0 -0
  97. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  98. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/package.json +0 -0
  99. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  100. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  101. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  102. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  103. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  104. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  105. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  106. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  107. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  108. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  109. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  110. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  111. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  112. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  113. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  114. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  115. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  116. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  117. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  118. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  119. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  120. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  121. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  122. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  123. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  124. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  125. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  126. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  127. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  128. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  129. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  130. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  131. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  132. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_architecture_analyzer.py +0 -0
  133. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_architecture_summary.py +0 -0
  134. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_ast_extractor.py +0 -0
  135. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_block1_reliability.py +0 -0
  136. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_block2_coverage.py +0 -0
  137. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_block5_quality.py +0 -0
  138. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_bug_fixes_v16.py +0 -0
  139. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_classifier.py +0 -0
  140. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_cli.py +0 -0
  141. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_code_notes_analyzer.py +0 -0
  142. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_context_scorer.py +0 -0
  143. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_contract_pipeline.py +0 -0
  144. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_coverage_parser.py +0 -0
  145. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_cross_consistency.py +0 -0
  146. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_dependency_analyzer_node_python.py +0 -0
  147. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_dependency_analyzer_polyglot.py +0 -0
  148. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_dependency_schema.py +0 -0
  149. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_dotnet.py +0 -0
  150. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_go_rust_java.py +0 -0
  151. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_nodejs.py +0 -0
  152. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_php_ruby_dart.py +0 -0
  153. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_python.py +0 -0
  154. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_universal_managed.py +0 -0
  155. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detector_universal_systems.py +0 -0
  156. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_detectors_base.py +0 -0
  157. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_doc_analyzer_jsdom.py +0 -0
  158. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_doc_analyzer_python.py +0 -0
  159. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_encoding_regression.py +0 -0
  160. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_graph_analyzer_polyglot.py +0 -0
  161. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_graph_analyzer_python_node.py +0 -0
  162. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_graph_schema.py +0 -0
  163. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_hybrid_inference.py +0 -0
  164. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration.py +0 -0
  165. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_dependencies.py +0 -0
  166. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_detection.py +0 -0
  167. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_docs.py +0 -0
  168. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_graph_modules.py +0 -0
  169. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_lqn.py +0 -0
  170. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_metrics.py +0 -0
  171. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_multistack.py +0 -0
  172. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_semantics.py +0 -0
  173. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_integration_universal.py +0 -0
  174. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_java_spring_integration.py +0 -0
  175. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_metrics_analyzer.py +0 -0
  176. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_output_ux.py +0 -0
  177. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_packaging.py +0 -0
  178. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_phase1_improvements.py +0 -0
  179. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_pipeline_integrity.py +0 -0
  180. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_real_projects.py +0 -0
  181. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_redactor.py +0 -0
  182. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_repository_ir.py +0 -0
  183. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_scanner.py +0 -0
  184. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_schema.py +0 -0
  185. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_schema_normalization.py +0 -0
  186. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_semantic_analyzer_node.py +0 -0
  187. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_semantic_analyzer_python.py +0 -0
  188. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_semantic_import_resolution.py +0 -0
  189. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_semantic_schema.py +0 -0
  190. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_signal_hierarchy.py +0 -0
  191. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_summarizer.py +0 -0
  192. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_surface_honesty.py +0 -0
  193. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_task_differentiation.py +0 -0
  194. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_telemetry.py +0 -0
  195. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_v131_improvements.py +0 -0
  196. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_v1_10_regressions.py +0 -0
  197. {sourcecode-1.30.26 → sourcecode-1.30.28}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.30.26
3
+ Version: 1.30.28
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
221
221
 
222
222
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
223
223
 
224
- ![Version](https://img.shields.io/badge/version-1.30.26-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.28-blue)
225
225
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
226
226
 
227
227
  ---
@@ -257,7 +257,7 @@ pipx install sourcecode
257
257
 
258
258
  ```bash
259
259
  sourcecode version
260
- # sourcecode 1.30.26
260
+ # sourcecode 1.30.28
261
261
  ```
262
262
 
263
263
  ---
@@ -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.30.26-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.30.28-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.30.26
41
+ # sourcecode 1.30.28
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.30.26"
7
+ version = "1.30.28"
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.30.26"
3
+ __version__ = "1.30.28"
@@ -424,7 +424,7 @@ def main(
424
424
  no_redact: bool = typer.Option(
425
425
  False,
426
426
  "--no-redact",
427
- help="Disable secret redaction. Use with caution output may contain sensitive values.",
427
+ help="Disable secret redaction of output strings. Note: env var values from the OS are never included in output regardless of this flag (security policy).",
428
428
  ),
429
429
  version: Optional[bool] = typer.Option(
430
430
  None,
@@ -437,7 +437,7 @@ def main(
437
437
  depth: int = typer.Option(
438
438
  4,
439
439
  "--depth",
440
- help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to 12.",
440
+ help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to a minimum of 12; values below 12 have no effect on Java projects.",
441
441
  min=1,
442
442
  max=20,
443
443
  ),
@@ -1734,6 +1734,11 @@ def prepare_context_cmd(
1734
1734
  help="Emit per-phase timing to stderr (git scan ms, symptom scoring ms, total ms)",
1735
1735
  hidden=True,
1736
1736
  ),
1737
+ fast: bool = typer.Option(
1738
+ False,
1739
+ "--fast",
1740
+ help="Skip deep analysis (content search, test gap discovery, code annotations). Uses manifest/metadata only. Target: < 6 s.",
1741
+ ),
1737
1742
  ) -> None:
1738
1743
  """Task-specific context for AI coding agents.
1739
1744
 
@@ -1808,9 +1813,14 @@ def prepare_context_cmd(
1808
1813
  if since:
1809
1814
  _phase += f" since {since}"
1810
1815
  _progress.start(_phase)
1816
+ if not fast:
1817
+ import sys as _sys
1818
+ if _sys.stderr.isatty():
1819
+ _sys.stderr.write(f"Analyzing ({task})... (deep scan may take 15–35 s for large codebases)\n")
1820
+ _sys.stderr.flush()
1811
1821
  _t0 = _time.perf_counter()
1812
1822
  try:
1813
- output = builder.build(task, since=since, symptom=symptom)
1823
+ output = builder.build(task, since=since, symptom=symptom, fast=fast)
1814
1824
  finally:
1815
1825
  _progress.finish()
1816
1826
  _t_total = (_time.perf_counter() - _t0) * 1000
@@ -1960,8 +1970,8 @@ def prepare_context_cmd(
1960
1970
  _sys.stdout.buffer.write(b"\n")
1961
1971
  _sys.stdout.buffer.flush()
1962
1972
  if copy:
1963
- _copy_to_clipboard(_nc_json)
1964
- typer.echo("✓ copied to clipboard", err=True)
1973
+ if _copy_to_clipboard(_nc_json):
1974
+ typer.echo("✓ copied to clipboard", err=True)
1965
1975
  raise typer.Exit()
1966
1976
  if output.ci_decision:
1967
1977
  out["ci_decision"] = output.ci_decision
@@ -2082,6 +2092,10 @@ def prepare_context_cmd(
2082
2092
  out["symptom_note"] = output.symptom_note
2083
2093
  if output.symptom_explain:
2084
2094
  out["symptom_explain"] = output.symptom_explain
2095
+ if getattr(output, "symptom_hint", None):
2096
+ out["symptom_hint"] = output.symptom_hint
2097
+ if getattr(output, "warnings", None):
2098
+ out["warnings"] = output.warnings
2085
2099
  if llm_prompt:
2086
2100
  out["llm_prompt"] = builder.render_prompt(output)
2087
2101
 
@@ -335,6 +335,7 @@ class TaskOutput:
335
335
  related_notes: list[dict] = field(default_factory=list) # fix-bug + symptom only
336
336
  symptom_note: Optional[str] = None # fix-bug: cross-layer synonym note
337
337
  symptom_explain: Optional[dict] = None # fix-bug: structured evidence breakdown
338
+ symptom_hint: Optional[str] = None # fix-bug: redirect hint when term not found in this module
338
339
  # delta-specific impact fields
339
340
  impact_summary: Optional[str] = None
340
341
  affected_modules: list[str] = field(default_factory=list)
@@ -348,6 +349,7 @@ class TaskOutput:
348
349
  error_code: Optional[str] = None
349
350
  error_message: Optional[str] = None
350
351
  error_hints: list[str] = field(default_factory=list)
352
+ warnings: list[dict] = field(default_factory=list) # structured warnings (REF_NOT_FOUND, etc.)
351
353
  # CI decision state machine — machine-decidable signal
352
354
  ci_decision: Optional[str] = None # "no_changes" | "analysis_success" | "git_ref_error" | "no_git_repo"
353
355
  # git baseline resolution metadata
@@ -683,7 +685,7 @@ class TaskContextBuilder:
683
685
  def __init__(self, root: Path) -> None:
684
686
  self.root = root
685
687
 
686
- def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None) -> TaskOutput:
688
+ def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False) -> TaskOutput:
687
689
  if task_name not in TASKS:
688
690
  raise ValueError(
689
691
  f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
@@ -880,7 +882,7 @@ class TaskContextBuilder:
880
882
  improvement_opportunities: list[str] = []
881
883
  cn_notes_for_ranking: list = []
882
884
 
883
- if spec.enable_code_notes:
885
+ if spec.enable_code_notes and not fast:
884
886
  from dataclasses import asdict
885
887
  from sourcecode.code_notes_analyzer import CodeNotesAnalyzer
886
888
 
@@ -1317,6 +1319,7 @@ class TaskContextBuilder:
1317
1319
  related_notes: list[dict] = []
1318
1320
  symptom_note: Optional[str] = None
1319
1321
  symptom_explain: Optional[dict] = None
1322
+ symptom_hint: Optional[str] = None
1320
1323
  if task_name == "fix-bug" and symptom:
1321
1324
  import re as _re
1322
1325
  _camel_expanded = _re.sub(r'([a-z])([A-Z])', r'\1 \2', symptom)
@@ -1537,9 +1540,30 @@ class TaskContextBuilder:
1537
1540
  ),
1538
1541
  }
1539
1542
 
1543
+ # BUG #4: LOW confidence + 0 content matches → clear suspected_areas,
1544
+ # emit actionable redirect instead of unrelated files.
1545
+ if _sx_confidence == "LOW" and not _sx_content:
1546
+ suspected_areas = []
1547
+ _is_fe_term = any(kw in _FRONTEND_SYMPTOM_MAP for kw in symptom_keywords)
1548
+ _root_name = self.root.name
1549
+ if _is_fe_term:
1550
+ _fe_redirect = (
1551
+ f"Term {symptom!r} not found in sources under {_root_name!r}. "
1552
+ f"This appears to be a frontend symptom. "
1553
+ f"Try: prepare-context fix-bug . --symptom {symptom!r} "
1554
+ f"(monorepo root) or target a frontend sub-project directly."
1555
+ )
1556
+ else:
1557
+ _fe_redirect = (
1558
+ f"Term {symptom!r} not found in sources under {_root_name!r}. "
1559
+ f"Verify the spelling or try a related term. "
1560
+ f"If this is a frontend symptom, run against the frontend sub-project."
1561
+ )
1562
+ symptom_hint = _fe_redirect
1563
+
1540
1564
  # ── 7. Test gaps (generate-tests only) ────────────────────────────
1541
1565
  test_gaps: list[str] = []
1542
- if task_name == "generate-tests":
1566
+ if task_name == "generate-tests" and not fast:
1543
1567
  def _normalize_test_stem(stem: str) -> str:
1544
1568
  # Java: FooTest / FooTests → Foo; TestFoo → Foo
1545
1569
  if stem.endswith("Tests"):
@@ -1683,6 +1707,8 @@ class TaskContextBuilder:
1683
1707
  resolved_since_ref=_delta_baseline.get("resolved_ref") if task_name == "delta" else None,
1684
1708
  resolution_path=_delta_baseline.get("resolution_path") if task_name == "delta" else None,
1685
1709
  diff_validation_status=_delta_baseline.get("diff_validation_status") if task_name == "delta" else None,
1710
+ warnings=_delta_baseline.get("warnings", []) if task_name == "delta" else [],
1711
+ symptom_hint=symptom_hint if task_name == "fix-bug" else None,
1686
1712
  )
1687
1713
 
1688
1714
  def render_prompt(self, output: TaskOutput) -> str:
@@ -3261,6 +3287,7 @@ class TaskContextBuilder:
3261
3287
  "resolution_path": "head_minus_1_fallback",
3262
3288
  "diff_validation_status": "invalid_ref", # original ref unresolved
3263
3289
  "error": False,
3290
+ "warnings": [{"code": "REF_NOT_FOUND", "ref": since, "resolved_to": "HEAD~1"}],
3264
3291
  }
3265
3292
 
3266
3293
  # All stages failed
@@ -251,3 +251,95 @@ class RankingEngine:
251
251
 
252
252
  def is_auxiliary(self, path: str) -> bool:
253
253
  return self._scorer.is_auxiliary(path)
254
+
255
+
256
+ # ---------------------------------------------------------------------------
257
+ # Mandatory scoring formula — deterministic 5-component impact model
258
+ # ---------------------------------------------------------------------------
259
+
260
+ # runtime_impact: execution-path role of the file
261
+ _RUNTIME_IMPACT: dict[str | None, float] = {
262
+ "api_endpoint": 1.0, # @RestController / @Controller
263
+ "security": 1.0, # @EnableWebSecurity / security filters
264
+ "runtime_core": 1.0, # confirmed production entrypoint
265
+ "cli_entrypoint": 1.0,
266
+ "exception_handler": 0.8, # @ControllerAdvice
267
+ "business_logic": 0.7, # @Service (with or without @Transactional)
268
+ "api_layer": 0.7, # API framework import (non-annotation evidence)
269
+ "data_access": 0.5, # @Repository / @Mapper
270
+ "database_layer": 0.5, # DB framework import
271
+ "infrastructure": 0.5, # infra dependency import
272
+ "configuration": 0.4, # @Configuration
273
+ "application_logic": 0.3, # code defs + imports, no framework annotation
274
+ "domain_model": 0.3, # @Entity / domain models
275
+ "dto": 0.2, # @Data / pure data carriers
276
+ "build_system": 0.15,
277
+ "tests": 0.1,
278
+ "tooling": 0.05,
279
+ None: 0.15, # unclassified source file
280
+ }
281
+
282
+ # framework_signal_strength: annotation / import evidence quality
283
+ # Spring annotation → 0.3; security component → +0.2 (total 0.5); import only → 0.2
284
+ _FRAMEWORK_SIGNAL: dict[str | None, float] = {
285
+ "security": 0.5, # Spring Security annotation + security component
286
+ "api_endpoint": 0.3, # @RestController / @Controller
287
+ "exception_handler": 0.3, # @ControllerAdvice
288
+ "business_logic": 0.3, # @Service
289
+ "data_access": 0.3, # @Repository / @Mapper
290
+ "configuration": 0.3, # @Configuration
291
+ "domain_model": 0.3, # JPA @Entity
292
+ "dto": 0.3, # Lombok @Data
293
+ "api_layer": 0.2, # framework import (weaker than annotation)
294
+ "database_layer": 0.2,
295
+ "infrastructure": 0.2,
296
+ }
297
+
298
+ # Normalization ceiling per evidence tier — used when spread < 0.40
299
+ _NORM_TARGET_HI: dict[str, float] = {
300
+ "api_endpoint": 0.90,
301
+ "security": 0.90,
302
+ "exception_handler": 0.82,
303
+ "business_logic": 0.80,
304
+ "api_layer": 0.80,
305
+ "data_access": 0.70,
306
+ "database_layer": 0.70,
307
+ "infrastructure": 0.70,
308
+ "configuration": 0.65,
309
+ }
310
+
311
+
312
+ def resolve_runtime_impact(category: str | None) -> float:
313
+ """Map FileClassifier category → runtime_impact [0.0, 1.0]."""
314
+ return _RUNTIME_IMPACT.get(category, _RUNTIME_IMPACT[None])
315
+
316
+
317
+ def resolve_framework_signal(category: str | None) -> float:
318
+ """Map FileClassifier category → framework_signal_strength [0.0, 0.5]."""
319
+ return _FRAMEWORK_SIGNAL.get(category, 0.0)
320
+
321
+
322
+ def compute_impact_score(
323
+ runtime_impact: float,
324
+ dependency_centrality: float,
325
+ framework_signal_strength: float,
326
+ change_type_severity: float,
327
+ test_risk_factor: float,
328
+ ) -> float:
329
+ """Mandatory weighted scoring formula.
330
+
331
+ score = 0.35×runtime_impact + 0.25×dependency_centrality
332
+ + 0.20×framework_signal_strength + 0.10×change_type_severity
333
+ + 0.10×test_risk_factor
334
+
335
+ All inputs [0.0, 1.0]. Output clamped [0.0, 1.0].
336
+ Deterministic: same inputs always produce same output.
337
+ """
338
+ raw = (
339
+ 0.35 * runtime_impact
340
+ + 0.25 * dependency_centrality
341
+ + 0.20 * framework_signal_strength
342
+ + 0.10 * change_type_severity
343
+ + 0.10 * test_risk_factor
344
+ )
345
+ return max(0.0, min(1.0, raw))
@@ -248,6 +248,16 @@ def _compact_git_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
248
248
  for f, n in _fc.most_common(5)
249
249
  ]
250
250
  ctx["hotspots_source"] = "recent_commits"
251
+ if gc.recent_commits:
252
+ ctx["recent_commits"] = [
253
+ {
254
+ "hash": c.hash[:8],
255
+ "message": (c.message or "")[:80],
256
+ "date": (c.date or "")[:10],
257
+ "author": c.author or "",
258
+ }
259
+ for c in gc.recent_commits[:5]
260
+ ]
251
261
  return ctx if ctx else None
252
262
 
253
263
 
@@ -802,13 +812,13 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
802
812
  semantic_hub_scores[p] = h.get("importance_score", 0.0) / max_importance
803
813
 
804
814
  entry_paths = {ep.path for ep in sm.entry_points}
805
- # REST/MVC controllers are HTTP surface — surface before @Transactional services
806
- _rest_ctrl_paths = {
807
- ep.path for ep in sm.entry_points
808
- if getattr(ep, "kind", "") in {"rest_controller", "mvc_controller"}
809
- }
810
815
  scored: list[tuple[float, dict[str, Any]]] = []
811
816
 
817
+ from sourcecode.ranking_engine import (
818
+ compute_impact_score, resolve_runtime_impact, resolve_framework_signal,
819
+ _NORM_TARGET_HI,
820
+ )
821
+
812
822
  for path in sm.file_paths:
813
823
  file_class = classifier.classify(path)
814
824
  fs = engine.score(
@@ -821,78 +831,64 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
821
831
  if fs.score < -50: # hard noise
822
832
  continue
823
833
 
824
- content_rel = file_class.relevance if file_class else 0.0
825
- # Semantic hub bonus: normalised call-graph centrality adds up to +0.30
826
- sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
827
- pre_bonus_combined = fs.score + content_rel + sem_hub
828
- # REST controller boost: surface above @Transactional service files
829
- if path in _rest_ctrl_paths:
830
- pre_bonus_combined += 2.0
831
-
832
- # M3: Structural importance scoring — SORT ORDER ONLY.
833
- # These bonuses drive ranking but must NOT inflate the displayed score.
834
834
  stem = Path(path).stem
835
- stem_lower = stem.lower()
836
- path_lower = path.lower()
837
- sort_bonus = 0.0
838
- # +10 application entry points
839
- if path in entry_paths and any(
840
- k in stem for k in ("Application", "Main", "Initializer", "Bootstrap", "Startup")
841
- ):
842
- sort_bonus = 10.0
843
- # +8 known base/infrastructure classes
844
- elif any(k in stem for k in (
845
- "GenericRestController", "GenericCRUDRestController", "GenericController",
846
- "AkitaBaseService", "BaseService", "FilterConfig", "AbstractController",
847
- )):
848
- sort_bonus = 8.0
849
- # +6 security configuration
850
- elif any(k in stem for k in (
851
- "SecurityConfig", "SecurityStrategy", "WebSecurityConfig", "SecurityFilter",
852
- "JwtFilter", "AuthConfig", "SecurityConfiguration",
853
- )):
854
- sort_bonus = 6.0
855
- # +2 shared utilities
856
- elif any(k in path_lower for k in ("util", "shared", "common", "helper", "constant")):
857
- sort_bonus = 2.0
858
-
859
- sort_key = pre_bonus_combined + sort_bonus
860
-
861
- # Visibility threshold: require meaningful combined signal.
862
- # Exception: high/medium-confidence files with strong content relevance
863
- # can survive even if structural score is weak.
864
- if sort_key < _FILE_RELEVANCE_MIN_COMBINED:
835
+ cat = file_class.category if file_class else None
836
+
837
+ # ── Component 1: runtime_impact — execution-path role ─────────────────
838
+ runtime_impact = resolve_runtime_impact(cat)
839
+
840
+ # ── Component 2: dependency_centrality call-graph importance ─────────
841
+ # Entry points treat external HTTP/CLI callers as high centrality.
842
+ # Isolated files (no semantic data) score 0.0 — negative weighting by omission.
843
+ dep_centrality = semantic_hub_scores.get(path, 0.0)
844
+ if path in entry_paths:
845
+ dep_centrality = max(dep_centrality, 0.8)
846
+
847
+ # ── Component 3: framework_signal_strength — annotation quality ─────────
848
+ fw_signal = resolve_framework_signal(cat)
849
+
850
+ # ── Component 4: change_type_severity git churn as structural proxy ───
851
+ churn = git_churn.get(path, 0)
852
+ change_sev = min(churn / max(max_churn, 1), 1.0) * 0.8
853
+
854
+ # ── Component 5: test_risk_factor — no per-file coverage data ──────────
855
+ test_risk = 0.2
856
+
857
+ formula_raw = compute_impact_score(
858
+ runtime_impact, dep_centrality, fw_signal, change_sev, test_risk
859
+ )
860
+
861
+ # T1 override: confirmed production entrypoints → 0.92–1.00.
862
+ # Only runtime_core / cli_entrypoint categories justify scores ≥ 0.92.
863
+ if path in entry_paths and cat in ("runtime_core", "cli_entrypoint"):
864
+ score_val = round(
865
+ min(1.0, max(0.92, file_class.relevance if file_class else 0.92)), 3
866
+ )
867
+ else:
868
+ score_val = round(formula_raw, 3)
869
+
870
+ # Visibility threshold: formula score or high-relevance content exception.
871
+ if score_val < _FILE_RELEVANCE_MIN_COMBINED:
865
872
  if not (file_class
866
873
  and file_class.relevance > 0.45
867
874
  and file_class.confidence in {"high", "medium"}):
868
875
  continue
869
876
 
870
- # Suppress low-confidence auxiliary/config files unless structurally prominent
877
+ # Suppress low-confidence auxiliary/config files
871
878
  if (file_class
872
879
  and file_class.confidence == "low"
873
880
  and file_class.category in {"config", "auxiliary"}
874
- and sort_key < 0.45):
881
+ and score_val < 0.45):
875
882
  continue
876
883
 
877
- # Detect whether structural signals (fan_in, churn, etc.) are present
878
- # beyond the base path-relevance reason used by fallback tier.
879
- structural_signal_reasons = [
880
- r for r in fs.reasons
881
- if r not in ("source file", "workspace source root", "runtime entrypoint")
882
- and "path" not in r.lower()
883
- ]
884
- has_structural = bool(structural_signal_reasons)
885
-
886
- # Tiered display score: evidence-gated, tier-capped.
887
- # pre_bonus_combined is used (M3 sort_bonus excluded).
888
- score_val = _tiered_display_score(
889
- pre_bonus_combined, file_class, path, entry_paths, has_structural,
890
- )
891
- # relevance: raw evidence strength from FileClassifier (content signal only)
884
+ # relevance: content evidence only intentionally diverges from score
885
+ # when dep_centrality or churn are non-zero (score relevance invariant)
892
886
  relevance_val = round(file_class.relevance, 3) if file_class else round(
893
- min(0.39, max(0.10, pre_bonus_combined / 2.0)), 3
887
+ min(0.39, max(0.10, runtime_impact)), 3
894
888
  )
895
889
 
890
+ # sem_hub retained for signal reporting only — not used in score formula
891
+ sem_hub = semantic_hub_scores.get(path, 0.0) * 0.30
896
892
  ranking_reasons = [r for r in fs.reasons if r != "source file"]
897
893
  if sem_hub >= 0.15:
898
894
  ranking_reasons.append("call graph hub")
@@ -913,7 +909,7 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
913
909
  if ranking_reasons:
914
910
  item["ranking_reasons"] = ranking_reasons
915
911
 
916
- # Override metadata for known M3 base controller classes
912
+ # Override: universal base controller classes score as runtime_core
917
913
  if any(k in stem for k in ("GenericRestController", "GenericCRUDRestController")):
918
914
  item["category"] = "runtime_core"
919
915
  item["score"] = 0.95
@@ -926,11 +922,38 @@ def _file_relevance(sm: SourceMap, *, limit: int = _FILE_RELEVANCE_LIMIT) -> lis
926
922
  item["ranking_reasons"] = ["universal base class", "exception handling contract"]
927
923
  item["signals"] = [{"type": "framework_annotation", "strength": "strong"}]
928
924
 
929
- scored.append((sort_key, item))
925
+ # sort_key = final score (rank consistency: score order = output order)
926
+ scored.append((item["score"], item))
930
927
 
931
- # Deterministic sort: sort_key desc, then path asc
928
+ # Initial sort: score desc, path asc for deterministic tie-break
932
929
  scored.sort(key=lambda x: (-x[0], x[1]["path"]))
933
930
 
931
+ # Normalization: enforce minimum spread ≥ 0.4 among non-T1 files.
932
+ # T1 files (confirmed entrypoints, 0.92–1.0) are excluded — already correct.
933
+ # Prevents score compression when structural signals (--semantics, git) absent.
934
+ _nonep = [(sk, it) for sk, it in scored if it["path"] not in entry_paths]
935
+ if len(_nonep) > 1:
936
+ _vals = [it["score"] for _, it in _nonep]
937
+ _lo, _hi = min(_vals), max(_vals)
938
+ _spread = _hi - _lo
939
+ if _spread < 0.40:
940
+ _top_cat = max(_nonep, key=lambda x: x[1]["score"])[1].get("category", "")
941
+ _target_hi = _NORM_TARGET_HI.get(_top_cat, 0.60)
942
+ _target_lo = max(0.10, _target_hi - 0.50)
943
+ if _spread > 0:
944
+ _scale = (_target_hi - _target_lo) / _spread
945
+ for _, it in _nonep:
946
+ it["score"] = round(
947
+ max(0.0, min(1.0, _target_lo + (it["score"] - _lo) * _scale)), 3
948
+ )
949
+ else:
950
+ _mid = round((_target_hi + _target_lo) / 2.0, 3)
951
+ for _, it in _nonep:
952
+ it["score"] = _mid
953
+
954
+ # Re-sort by final score to guarantee rank consistency after normalization
955
+ scored.sort(key=lambda x: (-x[1]["score"], x[1]["path"]))
956
+
934
957
  # Diversity cap: at most half the budget from any single category.
935
958
  # Prevents 10/10 controllers drowning out services, repositories, domain.
936
959
  _CAT_CAP = max(1, limit // 2)