sourcecode 1.23.0__tar.gz → 1.26.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 (189) hide show
  1. {sourcecode-1.23.0 → sourcecode-1.26.0}/PKG-INFO +56 -11
  2. {sourcecode-1.23.0 → sourcecode-1.26.0}/README.md +55 -10
  3. {sourcecode-1.23.0 → sourcecode-1.26.0}/pyproject.toml +1 -1
  4. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/cli.py +19 -0
  6. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/prepare_context.py +163 -107
  7. {sourcecode-1.23.0 → sourcecode-1.26.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  8. {sourcecode-1.23.0 → sourcecode-1.26.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  9. {sourcecode-1.23.0 → sourcecode-1.26.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  10. {sourcecode-1.23.0 → sourcecode-1.26.0}/.continue-here.md +0 -0
  11. {sourcecode-1.23.0 → sourcecode-1.26.0}/.github/workflows/build-windows.yml +0 -0
  12. {sourcecode-1.23.0 → sourcecode-1.26.0}/.gitignore +0 -0
  13. {sourcecode-1.23.0 → sourcecode-1.26.0}/.ruff.toml +0 -0
  14. {sourcecode-1.23.0 → sourcecode-1.26.0}/CONTRIBUTING.md +0 -0
  15. {sourcecode-1.23.0 → sourcecode-1.26.0}/LICENSE +0 -0
  16. {sourcecode-1.23.0 → sourcecode-1.26.0}/SECURITY.md +0 -0
  17. {sourcecode-1.23.0 → sourcecode-1.26.0}/docs/privacy.md +0 -0
  18. {sourcecode-1.23.0 → sourcecode-1.26.0}/docs/schema.md +0 -0
  19. {sourcecode-1.23.0 → sourcecode-1.26.0}/raw +0 -0
  20. {sourcecode-1.23.0 → sourcecode-1.26.0}/run_cli.py +0 -0
  21. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/adaptive_scanner.py +0 -0
  22. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/architecture_analyzer.py +0 -0
  23. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/architecture_summary.py +0 -0
  24. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/ast_extractor.py +0 -0
  25. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/classifier.py +0 -0
  26. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  27. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/confidence_analyzer.py +0 -0
  28. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/context_scorer.py +0 -0
  29. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/context_summarizer.py +0 -0
  30. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/contract_model.py +0 -0
  31. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/contract_pipeline.py +0 -0
  32. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/coverage_parser.py +0 -0
  33. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/dependency_analyzer.py +0 -0
  34. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/__init__.py +0 -0
  35. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/base.py +0 -0
  36. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  37. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/dart.py +0 -0
  38. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/dotnet.py +0 -0
  39. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/elixir.py +0 -0
  40. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/go.py +0 -0
  41. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/heuristic.py +0 -0
  42. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/hybrid.py +0 -0
  43. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/java.py +0 -0
  44. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  45. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/nodejs.py +0 -0
  46. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/parsers.py +0 -0
  47. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/php.py +0 -0
  48. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/project.py +0 -0
  49. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/python.py +0 -0
  50. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/ruby.py +0 -0
  51. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/rust.py +0 -0
  52. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/systems.py +0 -0
  53. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/terraform.py +0 -0
  54. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/detectors/tooling.py +0 -0
  55. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/doc_analyzer.py +0 -0
  56. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  57. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/env_analyzer.py +0 -0
  58. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/file_classifier.py +0 -0
  59. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/git_analyzer.py +0 -0
  60. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/graph_analyzer.py +0 -0
  61. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/metrics_analyzer.py +0 -0
  62. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/progress.py +0 -0
  63. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/ranking_engine.py +0 -0
  64. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/redactor.py +0 -0
  65. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/relevance_scorer.py +0 -0
  66. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/repo_classifier.py +0 -0
  67. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/runtime_classifier.py +0 -0
  68. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/scanner.py +0 -0
  69. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/schema.py +0 -0
  70. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/semantic_analyzer.py +0 -0
  71. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/serializer.py +0 -0
  72. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/summarizer.py +0 -0
  73. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/__init__.py +0 -0
  74. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/config.py +0 -0
  75. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/consent.py +0 -0
  76. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/events.py +0 -0
  77. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/filters.py +0 -0
  78. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/telemetry/transport.py +0 -0
  79. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/tree_utils.py +0 -0
  80. {sourcecode-1.23.0 → sourcecode-1.26.0}/src/sourcecode/workspace.py +0 -0
  81. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/__init__.py +0 -0
  82. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/conftest.py +0 -0
  83. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/coverage.xml +0 -0
  84. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  85. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  86. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  87. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/go_service/go.mod +0 -0
  88. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/jacoco.xml +0 -0
  89. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/latin1_sample.java +0 -0
  90. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/latin1_sample_iso.java +0 -0
  91. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/lcov.info +0 -0
  92. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  93. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/nextjs_app/package.json +0 -0
  94. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  95. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  96. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  97. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  98. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  99. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  100. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  101. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  102. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  103. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  104. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  105. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  106. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  107. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  108. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  109. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  110. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  111. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  112. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  113. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  114. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  115. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  116. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  117. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  118. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  119. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  120. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  121. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  122. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  123. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  124. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  125. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  126. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  127. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_architecture_analyzer.py +0 -0
  128. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_architecture_summary.py +0 -0
  129. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_ast_extractor.py +0 -0
  130. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_block1_reliability.py +0 -0
  131. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_block2_coverage.py +0 -0
  132. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_block5_quality.py +0 -0
  133. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_bug_fixes_v16.py +0 -0
  134. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_classifier.py +0 -0
  135. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_cli.py +0 -0
  136. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_code_notes_analyzer.py +0 -0
  137. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_context_scorer.py +0 -0
  138. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_contract_pipeline.py +0 -0
  139. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_coverage_parser.py +0 -0
  140. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_cross_consistency.py +0 -0
  141. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  142. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  143. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_dependency_schema.py +0 -0
  144. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_dotnet.py +0 -0
  145. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_go_rust_java.py +0 -0
  146. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_nodejs.py +0 -0
  147. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_php_ruby_dart.py +0 -0
  148. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_python.py +0 -0
  149. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_universal_managed.py +0 -0
  150. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detector_universal_systems.py +0 -0
  151. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_detectors_base.py +0 -0
  152. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  153. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_doc_analyzer_python.py +0 -0
  154. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_encoding_regression.py +0 -0
  155. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  156. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_graph_analyzer_python_node.py +0 -0
  157. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_graph_schema.py +0 -0
  158. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_hybrid_inference.py +0 -0
  159. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration.py +0 -0
  160. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_dependencies.py +0 -0
  161. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_detection.py +0 -0
  162. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_docs.py +0 -0
  163. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_graph_modules.py +0 -0
  164. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_lqn.py +0 -0
  165. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_metrics.py +0 -0
  166. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_multistack.py +0 -0
  167. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_semantics.py +0 -0
  168. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_integration_universal.py +0 -0
  169. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_java_spring_integration.py +0 -0
  170. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_metrics_analyzer.py +0 -0
  171. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_packaging.py +0 -0
  172. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_phase1_improvements.py +0 -0
  173. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_pipeline_integrity.py +0 -0
  174. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_real_projects.py +0 -0
  175. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_redactor.py +0 -0
  176. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_scanner.py +0 -0
  177. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_schema.py +0 -0
  178. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_schema_normalization.py +0 -0
  179. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_semantic_analyzer_node.py +0 -0
  180. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_semantic_analyzer_python.py +0 -0
  181. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_semantic_import_resolution.py +0 -0
  182. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_semantic_schema.py +0 -0
  183. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_signal_hierarchy.py +0 -0
  184. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_summarizer.py +0 -0
  185. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_surface_honesty.py +0 -0
  186. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_task_differentiation.py +0 -0
  187. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_telemetry.py +0 -0
  188. {sourcecode-1.23.0 → sourcecode-1.26.0}/tests/test_v1_10_regressions.py +0 -0
  189. {sourcecode-1.23.0 → sourcecode-1.26.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.23.0
3
+ Version: 1.26.0
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
  **Compressed AI-ready context for Java/Spring enterprise codebases.**
223
223
 
224
- ![Version](https://img.shields.io/badge/version-1.23.0-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.26.0-blue)
225
225
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
226
226
 
227
227
  ---
@@ -255,7 +255,7 @@ pipx install sourcecode
255
255
 
256
256
  ```bash
257
257
  sourcecode version
258
- # sourcecode 1.23.0
258
+ # sourcecode 1.26.0
259
259
  ```
260
260
 
261
261
  ---
@@ -263,7 +263,7 @@ sourcecode version
263
263
  ## Quickstart
264
264
 
265
265
  ```bash
266
- # High-signal summary (~600-800 tokens) — recommended starting point
266
+ # High-signal summary (1000–3000 tokens depending on repo size) — recommended starting point
267
267
  sourcecode --compact
268
268
 
269
269
  # Add git hotspots and uncommitted file count
@@ -311,8 +311,9 @@ Example output for a Spring Boot project (`--compact`):
311
311
 
312
312
  | Flag | Alias | Default | Description |
313
313
  |------|-------|---------|-------------|
314
- | `--compact` | | off | **Recommended.** ~600-800 token summary: stack, entry points, dependencies, risk flags, confidence, gaps. Optimized for agent context windows. |
314
+ | `--compact` | | off | High-signal summary (1000–3000 tokens): stacks, entry points, dependencies, risk flags, confidence, gaps. Includes `security_surface`, `mybatis`, and `transactional_boundaries` for Java projects. |
315
315
  | `--agent` | | off | Structured noise-free JSON for AI agents: identity, entry points, dependencies, confidence, gaps. Auto-enables dependency, env-var, and code-notes analysis. |
316
+ | `--full` | | off | Remove truncation limits on `transactional_boundaries`, `mybatis.dto_mappers`, and other capped lists. |
316
317
  | `--git-context` | `-g` | off | Include git activity: recent commits, change hotspots, and uncommitted changes. |
317
318
  | `--changed-only` | | off | Limit output to git-modified files (staged, unstaged, untracked). Forces compact output. |
318
319
  | `--depth` | | `4` | File tree traversal depth (1–20). Java/Maven projects auto-adjust to 12. |
@@ -342,16 +343,18 @@ sourcecode prepare-context TASK [PATH] [OPTIONS]
342
343
  | `refactor` | Structural problems, improvement opportunities, high-annotation files | Code quality review |
343
344
  | `generate-tests` | Source files without test pairs, coverage gap analysis | Writing missing tests |
344
345
  | `review-pr` | Uncommitted/changed files + architectural impact | Pre-merge review |
345
- | `delta` | Only files changed in a git range (`--since`), affected entry points | Incremental CI context |
346
+ | `delta` | Changed files with multi-hop impact analysis, structural import graph, system-level impact summary | Incremental CI/review context |
346
347
 
347
348
  ### Options
348
349
 
349
350
  | Option | Description |
350
351
  |--------|-------------|
351
352
  | `--since REF` | Git ref for `delta` task (e.g. `HEAD~3`, `main`, `v1.2.0`). Required for `delta`; ignored for other tasks. |
353
+ | `--symptom TEXT` | *(fix-bug only)* Keyword hint for the bug — boosts matching files and surfaces related code notes. |
352
354
  | `--llm-prompt` | Append a ready-to-use LLM prompt to the output. |
353
355
  | `--dry-run` | Show what would be analyzed without running it. |
354
356
  | `--copy` / `-c` | Copy output to clipboard after a successful run. |
357
+ | `--output` / `-o` | Write output to a file. |
355
358
  | `--task-help` | List all tasks with descriptions and exit. |
356
359
 
357
360
  ### Examples
@@ -360,11 +363,8 @@ sourcecode prepare-context TASK [PATH] [OPTIONS]
360
363
  # Explain the current repo
361
364
  sourcecode prepare-context explain
362
365
 
363
- # Analyze a specific repo path
364
- sourcecode prepare-context explain /path/to/repo
365
-
366
- # Focus on bug-prone files
367
- sourcecode prepare-context fix-bug
366
+ # Focus on bug-prone files, with a symptom hint
367
+ sourcecode prepare-context fix-bug --symptom "NullPointerException in OrderService"
368
368
 
369
369
  # Incremental context: files changed since branch diverged from main
370
370
  sourcecode prepare-context delta . --since main
@@ -378,6 +378,51 @@ sourcecode prepare-context --task-help
378
378
 
379
379
  ---
380
380
 
381
+ ## `delta` — incremental impact analysis
382
+
383
+ The `delta` task is the recommended mode for CI pipelines and PR reviews. It goes beyond listing changed files: it builds a structural import graph and propagates impact transitively up to 3 hops.
384
+
385
+ ```bash
386
+ sourcecode prepare-context delta [PATH] --since REF
387
+ ```
388
+
389
+ **Output fields:**
390
+
391
+ | Field | Description |
392
+ |-------|-------------|
393
+ | `changed_files` | Files modified in the git range |
394
+ | `relevant_files` | Changed files + files pulled in by the import graph (scored by artifact type and hop distance) |
395
+ | `impact_summary` | Human-readable summary: artifact types changed and active risk areas |
396
+ | `affected_modules` | DDD domain modules touched by the change |
397
+ | `risk_areas` | Per-area severity breakdown (`security`, `api`, `persistence`, etc.) |
398
+ | `change_type` | Closed taxonomy: `behavioral_change`, `structural_change`, `configuration_change`, `dependency_change`, `security_change` |
399
+ | `system_impact` | Subsystems affected, behavioral changes, runtime impact notes |
400
+ | `dependency_graph_summary` | Verified structural import edges (hop 1–3) and `propagation_depth`. **Only real imports — no heuristics, no test files.** |
401
+ | `impact_score_per_file` | Per-file numeric impact score (0–1) |
402
+ | `since` | The git ref used |
403
+ | `gaps` | What the analysis could not determine |
404
+
405
+ **How the import graph works:**
406
+
407
+ 1. Each changed file is classified by artifact type (`controller`, `service`, `repository`, `security`, `spring_config`, etc.).
408
+ 2. A BFS traversal walks the import graph **repo-wide** (not restricted to the same module), up to 3 hops deep.
409
+ 3. `dependency_graph_summary.edges` only contains verified `import` / `@Autowired` / constructor-injection relationships. Test files and heuristic proximity matches are excluded from edges (they appear in `relevant_files` only if they have real imports of changed files).
410
+ 4. Score decays 30% per hop: a directly-changed `SecurityConfig.java` scores 0.90; its direct importer scores 0.63; a transitive importer scores 0.44.
411
+
412
+ ```bash
413
+ # Changed service → controller → facade (3 hops)
414
+ sourcecode prepare-context delta . --since main
415
+
416
+ # Output includes:
417
+ # dependency_graph_summary.edges:
418
+ # hop-1: OrderService.java → OrderRepository.java
419
+ # hop-2: OrderRepository.java → OrderController.java
420
+ # hop-3: OrderController.java → OrderFacade.java
421
+ # propagation_depth: 3
422
+ ```
423
+
424
+ ---
425
+
381
426
  ## Output schema
382
427
 
383
428
  All outputs include a `confidence_summary` block with `overall`, `stack`, and `entry_points` confidence levels (`high` / `medium` / `low`), plus an `analysis_gaps` list describing what could not be analyzed and why.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Compressed AI-ready context for Java/Spring enterprise codebases.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.23.0-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.26.0-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -36,7 +36,7 @@ pipx install sourcecode
36
36
 
37
37
  ```bash
38
38
  sourcecode version
39
- # sourcecode 1.23.0
39
+ # sourcecode 1.26.0
40
40
  ```
41
41
 
42
42
  ---
@@ -44,7 +44,7 @@ sourcecode version
44
44
  ## Quickstart
45
45
 
46
46
  ```bash
47
- # High-signal summary (~600-800 tokens) — recommended starting point
47
+ # High-signal summary (1000–3000 tokens depending on repo size) — recommended starting point
48
48
  sourcecode --compact
49
49
 
50
50
  # Add git hotspots and uncommitted file count
@@ -92,8 +92,9 @@ Example output for a Spring Boot project (`--compact`):
92
92
 
93
93
  | Flag | Alias | Default | Description |
94
94
  |------|-------|---------|-------------|
95
- | `--compact` | | off | **Recommended.** ~600-800 token summary: stack, entry points, dependencies, risk flags, confidence, gaps. Optimized for agent context windows. |
95
+ | `--compact` | | off | High-signal summary (1000–3000 tokens): stacks, entry points, dependencies, risk flags, confidence, gaps. Includes `security_surface`, `mybatis`, and `transactional_boundaries` for Java projects. |
96
96
  | `--agent` | | off | Structured noise-free JSON for AI agents: identity, entry points, dependencies, confidence, gaps. Auto-enables dependency, env-var, and code-notes analysis. |
97
+ | `--full` | | off | Remove truncation limits on `transactional_boundaries`, `mybatis.dto_mappers`, and other capped lists. |
97
98
  | `--git-context` | `-g` | off | Include git activity: recent commits, change hotspots, and uncommitted changes. |
98
99
  | `--changed-only` | | off | Limit output to git-modified files (staged, unstaged, untracked). Forces compact output. |
99
100
  | `--depth` | | `4` | File tree traversal depth (1–20). Java/Maven projects auto-adjust to 12. |
@@ -123,16 +124,18 @@ sourcecode prepare-context TASK [PATH] [OPTIONS]
123
124
  | `refactor` | Structural problems, improvement opportunities, high-annotation files | Code quality review |
124
125
  | `generate-tests` | Source files without test pairs, coverage gap analysis | Writing missing tests |
125
126
  | `review-pr` | Uncommitted/changed files + architectural impact | Pre-merge review |
126
- | `delta` | Only files changed in a git range (`--since`), affected entry points | Incremental CI context |
127
+ | `delta` | Changed files with multi-hop impact analysis, structural import graph, system-level impact summary | Incremental CI/review context |
127
128
 
128
129
  ### Options
129
130
 
130
131
  | Option | Description |
131
132
  |--------|-------------|
132
133
  | `--since REF` | Git ref for `delta` task (e.g. `HEAD~3`, `main`, `v1.2.0`). Required for `delta`; ignored for other tasks. |
134
+ | `--symptom TEXT` | *(fix-bug only)* Keyword hint for the bug — boosts matching files and surfaces related code notes. |
133
135
  | `--llm-prompt` | Append a ready-to-use LLM prompt to the output. |
134
136
  | `--dry-run` | Show what would be analyzed without running it. |
135
137
  | `--copy` / `-c` | Copy output to clipboard after a successful run. |
138
+ | `--output` / `-o` | Write output to a file. |
136
139
  | `--task-help` | List all tasks with descriptions and exit. |
137
140
 
138
141
  ### Examples
@@ -141,11 +144,8 @@ sourcecode prepare-context TASK [PATH] [OPTIONS]
141
144
  # Explain the current repo
142
145
  sourcecode prepare-context explain
143
146
 
144
- # Analyze a specific repo path
145
- sourcecode prepare-context explain /path/to/repo
146
-
147
- # Focus on bug-prone files
148
- sourcecode prepare-context fix-bug
147
+ # Focus on bug-prone files, with a symptom hint
148
+ sourcecode prepare-context fix-bug --symptom "NullPointerException in OrderService"
149
149
 
150
150
  # Incremental context: files changed since branch diverged from main
151
151
  sourcecode prepare-context delta . --since main
@@ -159,6 +159,51 @@ sourcecode prepare-context --task-help
159
159
 
160
160
  ---
161
161
 
162
+ ## `delta` — incremental impact analysis
163
+
164
+ The `delta` task is the recommended mode for CI pipelines and PR reviews. It goes beyond listing changed files: it builds a structural import graph and propagates impact transitively up to 3 hops.
165
+
166
+ ```bash
167
+ sourcecode prepare-context delta [PATH] --since REF
168
+ ```
169
+
170
+ **Output fields:**
171
+
172
+ | Field | Description |
173
+ |-------|-------------|
174
+ | `changed_files` | Files modified in the git range |
175
+ | `relevant_files` | Changed files + files pulled in by the import graph (scored by artifact type and hop distance) |
176
+ | `impact_summary` | Human-readable summary: artifact types changed and active risk areas |
177
+ | `affected_modules` | DDD domain modules touched by the change |
178
+ | `risk_areas` | Per-area severity breakdown (`security`, `api`, `persistence`, etc.) |
179
+ | `change_type` | Closed taxonomy: `behavioral_change`, `structural_change`, `configuration_change`, `dependency_change`, `security_change` |
180
+ | `system_impact` | Subsystems affected, behavioral changes, runtime impact notes |
181
+ | `dependency_graph_summary` | Verified structural import edges (hop 1–3) and `propagation_depth`. **Only real imports — no heuristics, no test files.** |
182
+ | `impact_score_per_file` | Per-file numeric impact score (0–1) |
183
+ | `since` | The git ref used |
184
+ | `gaps` | What the analysis could not determine |
185
+
186
+ **How the import graph works:**
187
+
188
+ 1. Each changed file is classified by artifact type (`controller`, `service`, `repository`, `security`, `spring_config`, etc.).
189
+ 2. A BFS traversal walks the import graph **repo-wide** (not restricted to the same module), up to 3 hops deep.
190
+ 3. `dependency_graph_summary.edges` only contains verified `import` / `@Autowired` / constructor-injection relationships. Test files and heuristic proximity matches are excluded from edges (they appear in `relevant_files` only if they have real imports of changed files).
191
+ 4. Score decays 30% per hop: a directly-changed `SecurityConfig.java` scores 0.90; its direct importer scores 0.63; a transitive importer scores 0.44.
192
+
193
+ ```bash
194
+ # Changed service → controller → facade (3 hops)
195
+ sourcecode prepare-context delta . --since main
196
+
197
+ # Output includes:
198
+ # dependency_graph_summary.edges:
199
+ # hop-1: OrderService.java → OrderRepository.java
200
+ # hop-2: OrderRepository.java → OrderController.java
201
+ # hop-3: OrderController.java → OrderFacade.java
202
+ # propagation_depth: 3
203
+ ```
204
+
205
+ ---
206
+
162
207
  ## Output schema
163
208
 
164
209
  All outputs include a `confidence_summary` block with `overall`, `stack`, and `entry_points` confidence levels (`high` / `medium` / `low`), plus an `analysis_gaps` list describing what could not be analyzed and why.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.23.0"
7
+ version = "1.26.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.23.0"
3
+ __version__ = "1.26.0"
@@ -1773,6 +1773,25 @@ def prepare_context_cmd(
1773
1773
  out["affected_entry_points"] = output.affected_entry_points
1774
1774
  # Delta-specific impact fields
1775
1775
  if task == "delta":
1776
+ if output.error_code:
1777
+ # Hard error — emit structured error JSON and exit, skip normal delta fields
1778
+ _err_out: dict[str, Any] = {
1779
+ "task": output.task,
1780
+ "error": output.error_code,
1781
+ "since": output.since,
1782
+ "message": output.error_message,
1783
+ }
1784
+ if output.error_hints:
1785
+ _err_out["hint"] = output.error_hints
1786
+ _err_json = json.dumps(_err_out, indent=2, ensure_ascii=False)
1787
+ if output_path is not None:
1788
+ output_path.write_text(_err_json, encoding="utf-8")
1789
+ else:
1790
+ import sys as _sys
1791
+ _sys.stdout.buffer.write(_err_json.encode("utf-8"))
1792
+ _sys.stdout.buffer.write(b"\n")
1793
+ _sys.stdout.buffer.flush()
1794
+ raise typer.Exit(code=1)
1776
1795
  if output.since:
1777
1796
  out["since"] = output.since
1778
1797
  if output.impact_summary:
@@ -333,6 +333,10 @@ class TaskOutput:
333
333
  change_type: list[str] = field(default_factory=list)
334
334
  dependency_graph_summary: dict = field(default_factory=dict)
335
335
  impact_score_per_file: dict = field(default_factory=dict)
336
+ # error state (git ref not found, etc.)
337
+ error_code: Optional[str] = None
338
+ error_message: Optional[str] = None
339
+ error_hints: list[str] = field(default_factory=list)
336
340
 
337
341
 
338
342
  # ─────────────────────────────────────────────────────────────────────────────
@@ -618,7 +622,34 @@ class TaskContextBuilder:
618
622
  _delta_files: Optional[set[str]] = None
619
623
  if task_name == "delta":
620
624
  _delta_raw = self._get_git_changed_files(since=since)
621
- if _delta_raw:
625
+ if _delta_raw is None:
626
+ # Explicit --since ref couldn't be resolved — hard error, no fallback
627
+ _avail_branches, _suggested = self._get_available_refs(since or "")
628
+ _hints: list[str] = []
629
+ if _suggested:
630
+ _hints.append(f"Did you mean '{_suggested}'?")
631
+ if _avail_branches:
632
+ _hints.append(f"Available refs: {', '.join(_avail_branches[:8])}")
633
+ return TaskOutput(
634
+ task="delta",
635
+ goal="Produce incremental context for changed files — avoids re-reading the full repo.",
636
+ project_summary=None,
637
+ architecture_summary=None,
638
+ relevant_files=[],
639
+ suspected_areas=[],
640
+ improvement_opportunities=[],
641
+ test_gaps=[],
642
+ key_dependencies=[],
643
+ code_notes_summary=None,
644
+ limitations=[],
645
+ confidence="low",
646
+ since=since,
647
+ error_code="git_ref_not_found",
648
+ error_message=f"Git reference '{since}' does not exist in this repository.",
649
+ error_hints=_hints,
650
+ gaps=[f"Cannot compute delta: git ref '{since}' not found."] + _hints,
651
+ )
652
+ elif _delta_raw:
622
653
  _delta_files = set(_delta_raw)
623
654
 
624
655
  # ── 5c. review-pr suspected_areas (needs git uncommitted_files) ──────
@@ -1777,118 +1808,108 @@ class TaskContextBuilder:
1777
1808
  path=path, role=role, score=rel_score, reason=reason, why=why_str
1778
1809
  )))
1779
1810
  why[path] = why_str
1780
- for trigger_name in triggers[:3]:
1781
- trigger_full = next((f for f in changed_files if Path(f).name == trigger_name), None)
1782
- if trigger_full:
1783
- graph_edges.append({"from": trigger_full, "to": path, "edge_type": "module_proximity", "hop": 1})
1811
+ # module_proximity is heuristic (not a verified import) — excluded from structural graph
1784
1812
 
1785
1813
  related.sort(key=lambda x: (-x[0], x[1]))
1786
1814
  relevant.extend(rf for _, _, rf in related[:10])
1787
1815
 
1788
- # ── Step 3b: import-link expansion (bounded file scan) ────────────────
1789
- # Find files in the same module that import/reference a changed file.
1790
- # More precise than directory proximity: based on actual import statements.
1791
- _import_candidates = [
1816
+ # ── Steps 3b–3c: BFS multi-hop import propagation (repo-wide, 3 hops max)
1817
+ # Each hop expands from whatever was found in the previous hop into ALL
1818
+ # remaining source files not restricted to original module/dir.
1819
+ # This enables A→B→C discovery even when B and C are in different modules.
1820
+ _BFS_SCANNABLE = frozenset({".py", ".java", ".kt", ".ts", ".js", ".tsx", ".jsx", ".mjs"})
1821
+ _bfs_all_sources = [
1792
1822
  p for p in all_paths
1793
- if p not in existing_paths and Path(p).suffix.lower() in {
1794
- ".py", ".java", ".kt", ".ts", ".js", ".tsx", ".jsx", ".mjs"
1795
- }
1796
- and (
1797
- _extract_ddd_domain(p) in affected_modules_set
1798
- or str(Path(p).parent).replace("\\", "/") in changed_dirs
1799
- )
1823
+ if Path(p).suffix.lower() in _BFS_SCANNABLE
1824
+ ]
1825
+
1826
+ _bfs_seen: set[str] = {rf.path for rf in relevant}
1827
+ _bfs_frontier: list[str] = [
1828
+ f for f in changed_files
1829
+ if Path(f).suffix.lower() in _BFS_SCANNABLE
1800
1830
  ]
1801
- if _import_candidates:
1802
- _import_dep_map = self._scan_import_dependents(
1803
- changed_paths=changed_files,
1804
- candidate_paths=_import_candidates,
1831
+
1832
+ # (max results added from this hop, max_candidates scanned per seed)
1833
+ _BFS_HOP_BUDGET = [
1834
+ (8, 60), # hop 1 — broad: callers of changed files
1835
+ (6, 50), # hop 2 — transitives: callers of hop-1 files
1836
+ (4, 30), # hop 3 — deep transitives: callers of hop-2 files
1837
+ ]
1838
+
1839
+ # (hop_num, score, path, RelevantFile) — merged across all hops then added to relevant
1840
+ _bfs_collected: list[tuple[int, float, str, RelevantFile]] = []
1841
+
1842
+ for _hop_num, (_max_results, _max_cands) in enumerate(_BFS_HOP_BUDGET, start=1):
1843
+ if not _bfs_frontier:
1844
+ break
1845
+
1846
+ _bfs_candidates = [p for p in _bfs_all_sources if p not in _bfs_seen]
1847
+ if not _bfs_candidates:
1848
+ break
1849
+
1850
+ _hop_dep_map = self._scan_import_dependents(
1851
+ changed_paths=_bfs_frontier,
1852
+ candidate_paths=_bfs_candidates,
1853
+ max_candidates=_max_cands,
1805
1854
  )
1806
- _import_seen = {rf.path for rf in relevant}
1807
- _import_extra: list[tuple[float, str, RelevantFile]] = []
1808
- for changed_path, dep_paths in _import_dep_map.items():
1809
- changed_atype = classifications[changed_path]["artifact_type"]
1810
- for dep_path in dep_paths:
1811
- if dep_path in _import_seen:
1855
+
1856
+ # collect (score, path) pairs for this hop to build the next frontier
1857
+ _hop_scored: list[tuple[float, str]] = []
1858
+
1859
+ for _seed_path, _dep_paths in _hop_dep_map.items():
1860
+ _seed_atype = (
1861
+ classifications[_seed_path]["artifact_type"]
1862
+ if _seed_path in classifications
1863
+ else self._classify_changed_file(_seed_path)["artifact_type"]
1864
+ )
1865
+ for _dep_path in _dep_paths:
1866
+ if _dep_path in _bfs_seen:
1812
1867
  continue
1813
- dep_cls = self._classify_changed_file(dep_path)
1814
- if dep_cls["is_noise"]:
1868
+ _bfs_seen.add(_dep_path)
1869
+
1870
+ _dep_cls = self._classify_changed_file(_dep_path)
1871
+ if _dep_cls["is_noise"]:
1815
1872
  continue
1816
- dep_atype = dep_cls["artifact_type"]
1817
- dep_base = _ARTIFACT_SCORE.get(dep_atype, 0.45)
1818
- dep_score = round(dep_base * 0.70, 2)
1819
- dep_role = _role_in_system(dep_path, dep_atype, dep_path in ep_paths)
1820
- why_str = (
1821
- f"artifact_type: {dep_atype} | role_in_system: {dep_role}"
1822
- f" | impact_area: {_classify_impact_area(dep_path, dep_cls['risk_areas'], dep_atype)}"
1823
- f" | propagation_risk: {_PROPAGATION_RISK.get(dep_atype, 'low')}"
1824
- f" | change_effect: {_CHANGE_EFFECT.get(dep_atype, 'modifies application logic')}"
1825
- f" | pulled_by: import-link from {Path(changed_path).name}"
1873
+
1874
+ _dep_atype = _dep_cls["artifact_type"]
1875
+ _dep_score_base = _ARTIFACT_SCORE.get(_dep_atype, 0.45)
1876
+ # score decays 30% per hop so transitives rank below direct dependents
1877
+ _dep_score = round(_dep_score_base * (0.70 ** _hop_num), 2)
1878
+ _dep_role = _role_in_system(_dep_path, _dep_atype, _dep_path in ep_paths)
1879
+
1880
+ _why_str = (
1881
+ f"artifact_type: {_dep_atype} | role_in_system: {_dep_role}"
1882
+ f" | impact_area: {_classify_impact_area(_dep_path, _dep_cls['risk_areas'], _dep_atype)}"
1883
+ f" | propagation_risk: {_PROPAGATION_RISK.get(_dep_atype, 'low')}"
1884
+ f" | change_effect: {_CHANGE_EFFECT.get(_dep_atype, 'modifies application logic')}"
1885
+ f" | pulled_by: hop-{_hop_num} import from {Path(_seed_path).name}"
1826
1886
  )
1827
- reason = f"import-dependent of {Path(changed_path).name} ({changed_atype}) | score: {dep_score:.2f}"
1828
- _import_extra.append((dep_score, dep_path, RelevantFile(
1829
- path=dep_path, role=dep_role, score=dep_score,
1830
- reason=reason, why=why_str,
1887
+ _reason = (
1888
+ f"hop-{_hop_num} import-dependent of {Path(_seed_path).name}"
1889
+ f" ({_seed_atype}) | score: {_dep_score:.2f}"
1890
+ )
1891
+ why[_dep_path] = _why_str
1892
+ # Tests are consumers, not structural dependencies — exclude from import graph.
1893
+ # They remain in relevant_files but must not seed further BFS hops.
1894
+ _is_test = _dep_atype == "test"
1895
+ if not _is_test:
1896
+ graph_edges.append({
1897
+ "from": _seed_path, "to": _dep_path,
1898
+ "edge_type": "import_dependency", "hop": _hop_num,
1899
+ })
1900
+ _hop_scored.append((_dep_score, _dep_path))
1901
+ _bfs_collected.append((_hop_num, _dep_score, _dep_path, RelevantFile(
1902
+ path=_dep_path, role=_dep_role, score=_dep_score,
1903
+ reason=_reason, why=_why_str,
1831
1904
  )))
1832
- why[dep_path] = why_str
1833
- _import_seen.add(dep_path)
1834
- graph_edges.append({"from": changed_path, "to": dep_path, "edge_type": "import_dependency", "hop": 1})
1835
- _import_extra.sort(key=lambda x: (-x[0], x[1]))
1836
- relevant.extend(rf for _, _, rf in _import_extra[:8])
1837
-
1838
- # ── Step 3c: hop-2 import propagation (callers of hop-1 dependents) ────
1839
- _hop1_seeds = list(dict.fromkeys(
1840
- dep_path
1841
- for dep_paths in (_import_dep_map.values() if _import_candidates else [])
1842
- for dep_path in dep_paths
1843
- ))
1844
- if _hop1_seeds:
1845
- _hop2_seen = {rf.path for rf in relevant}
1846
- _hop2_candidates = [
1847
- p for p in all_paths
1848
- if p not in _hop2_seen
1849
- and Path(p).suffix.lower() in {".py", ".java", ".kt", ".ts", ".js", ".tsx", ".jsx", ".mjs"}
1850
- and (
1851
- _extract_ddd_domain(p) in affected_modules_set
1852
- or str(Path(p).parent).replace("\\", "/") in changed_dirs
1853
- )
1854
- ]
1855
- if _hop2_candidates:
1856
- _hop2_dep_map = self._scan_import_dependents(
1857
- changed_paths=_hop1_seeds,
1858
- candidate_paths=_hop2_candidates,
1859
- max_candidates=20,
1860
- )
1861
- _hop2_extra: list[tuple[float, str, RelevantFile]] = []
1862
- for hop1_path, dep_paths in _hop2_dep_map.items():
1863
- hop1_cls = self._classify_changed_file(hop1_path)
1864
- hop1_atype = hop1_cls["artifact_type"]
1865
- for dep_path in dep_paths:
1866
- if dep_path in _hop2_seen:
1867
- continue
1868
- dep_cls = self._classify_changed_file(dep_path)
1869
- if dep_cls["is_noise"]:
1870
- continue
1871
- dep_atype = dep_cls["artifact_type"]
1872
- dep_base = _ARTIFACT_SCORE.get(dep_atype, 0.45)
1873
- dep_score = round(dep_base * 0.50, 2)
1874
- dep_role = _role_in_system(dep_path, dep_atype, dep_path in ep_paths)
1875
- why_str = (
1876
- f"artifact_type: {dep_atype} | role_in_system: {dep_role}"
1877
- f" | impact_area: {_classify_impact_area(dep_path, dep_cls['risk_areas'], dep_atype)}"
1878
- f" | propagation_risk: {_PROPAGATION_RISK.get(dep_atype, 'low')}"
1879
- f" | change_effect: {_CHANGE_EFFECT.get(dep_atype, 'modifies application logic')}"
1880
- f" | pulled_by: hop-2 import from {Path(hop1_path).name}"
1881
- )
1882
- reason = f"hop-2 import-dependent of {Path(hop1_path).name} ({hop1_atype}) | score: {dep_score:.2f}"
1883
- _hop2_extra.append((dep_score, dep_path, RelevantFile(
1884
- path=dep_path, role=dep_role, score=dep_score,
1885
- reason=reason, why=why_str,
1886
- )))
1887
- why[dep_path] = why_str
1888
- _hop2_seen.add(dep_path)
1889
- graph_edges.append({"from": hop1_path, "to": dep_path, "edge_type": "import_dependency", "hop": 2})
1890
- _hop2_extra.sort(key=lambda x: (-x[0], x[1]))
1891
- relevant.extend(rf for _, _, rf in _hop2_extra[:5])
1905
+
1906
+ # next frontier = top-N files by score from this hop
1907
+ _hop_scored.sort(key=lambda x: -x[0])
1908
+ _bfs_frontier = [p for _, p in _hop_scored[:_max_results]]
1909
+
1910
+ # merge into relevant: closer hops first, then higher score; cap total at 20
1911
+ _bfs_collected.sort(key=lambda x: (x[0], -x[1], x[2]))
1912
+ relevant.extend(rf for _, _, _, rf in _bfs_collected[:20])
1892
1913
 
1893
1914
  # ── Step 3d: per-file impact scores, change_type, system_impact ─────────
1894
1915
  # Downstream fanout: count graph edges originating from each changed file
@@ -2054,9 +2075,13 @@ class TaskContextBuilder:
2054
2075
  }
2055
2076
 
2056
2077
  # ── Step 6: analysis gaps ──────────────────────────────────────────────
2057
- _hop_desc = f"+ hop-2 import propagation (propagation_depth={_max_hop})" if _max_hop >= 2 else ""
2078
+ _bfs_note = (
2079
+ f"BFS multi-hop import propagation repo-wide (propagation_depth={_max_hop})"
2080
+ if _max_hop >= 1
2081
+ else "no import-link propagation (no scannable changed files)"
2082
+ )
2058
2083
  analysis_gaps: list[str] = [
2059
- f"Related file expansion uses type-aware propagation chains + bounded import-link scanning {_hop_desc}+ module/directory heuristics".replace(" ", " ").strip(),
2084
+ f"Related file expansion: type-aware chain expansion + {_bfs_note} + module/directory heuristics",
2060
2085
  ]
2061
2086
  if noise_count > 0 and meaningful > 0:
2062
2087
  analysis_gaps.append(
@@ -2093,9 +2118,13 @@ class TaskContextBuilder:
2093
2118
  impact_score_per_file,
2094
2119
  )
2095
2120
 
2096
- def _get_git_changed_files(self, since: Optional[str] = None) -> list[str]:
2121
+ def _get_git_changed_files(self, since: Optional[str] = None) -> Optional[list[str]]:
2097
2122
  """Get files changed since a git ref (default: HEAD~1) relative to self.root.
2098
2123
 
2124
+ Returns None when `since` is explicitly provided but cannot be resolved —
2125
+ this is an error state, not "no changes". Callers must distinguish None
2126
+ (ref invalid) from [] (ref valid, no changes).
2127
+
2099
2128
  Uses --relative so paths are relative to cwd (self.root), not the git repo
2100
2129
  root. This is critical for monorepos where self.root is a subpath of the
2101
2130
  git root and git diff would otherwise return prefixed paths that don't match
@@ -2118,9 +2147,13 @@ class TaskContextBuilder:
2118
2147
  line.strip() for line in (result.stdout or "").splitlines()
2119
2148
  if line.strip()
2120
2149
  ]
2150
+ # Non-zero exit with explicit ref = ref doesn't exist — no silent fallback
2151
+ if since:
2152
+ return None
2121
2153
  except (subprocess.TimeoutExpired, FileNotFoundError):
2122
- pass
2123
- # Fallback: uncommitted changes
2154
+ if since:
2155
+ return None
2156
+ # No explicit since: fall back to uncommitted changes
2124
2157
  try:
2125
2158
  result = subprocess.run(
2126
2159
  ["git", "diff", "--name-only", "--relative"],
@@ -2136,3 +2169,26 @@ class TaskContextBuilder:
2136
2169
  except (subprocess.TimeoutExpired, FileNotFoundError):
2137
2170
  pass
2138
2171
  return []
2172
+
2173
+ def _get_available_refs(self, invalid_ref: str) -> tuple[list[str], Optional[str]]:
2174
+ """Return (available_branch_names, suggested_alternative) for error hints."""
2175
+ import subprocess
2176
+ branches: list[str] = []
2177
+ suggested: Optional[str] = None
2178
+ try:
2179
+ r = subprocess.run(
2180
+ ["git", "branch", "-a", "--format=%(refname:short)"],
2181
+ cwd=str(self.root),
2182
+ capture_output=True, text=True, timeout=5,
2183
+ )
2184
+ if r.returncode == 0:
2185
+ all_refs = [b.strip() for b in r.stdout.splitlines() if b.strip()]
2186
+ branches = [b for b in all_refs if "HEAD" not in b][:10]
2187
+ ref_lower = invalid_ref.lower()
2188
+ if ref_lower == "master" and any(b.rstrip("/").endswith("main") for b in all_refs):
2189
+ suggested = "main"
2190
+ elif ref_lower == "main" and any(b.rstrip("/").endswith("master") for b in all_refs):
2191
+ suggested = "master"
2192
+ except (subprocess.TimeoutExpired, FileNotFoundError):
2193
+ pass
2194
+ return branches, suggested
File without changes
File without changes
File without changes
File without changes