sourcecode 1.28.0__tar.gz → 1.30.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 (190) hide show
  1. sourcecode-1.30.0/.continue-here.md +118 -0
  2. {sourcecode-1.28.0 → sourcecode-1.30.0}/PKG-INFO +3 -3
  3. {sourcecode-1.28.0 → sourcecode-1.30.0}/README.md +2 -2
  4. {sourcecode-1.28.0 → sourcecode-1.30.0}/pyproject.toml +1 -1
  5. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/cli.py +58 -1
  7. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/prepare_context.py +197 -10
  8. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_v1_10_regressions.py +39 -13
  9. sourcecode-1.28.0/.continue-here.md +0 -108
  10. {sourcecode-1.28.0 → sourcecode-1.30.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  11. {sourcecode-1.28.0 → sourcecode-1.30.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  12. {sourcecode-1.28.0 → sourcecode-1.30.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  13. {sourcecode-1.28.0 → sourcecode-1.30.0}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-1.28.0 → sourcecode-1.30.0}/.gitignore +0 -0
  15. {sourcecode-1.28.0 → sourcecode-1.30.0}/.ruff.toml +0 -0
  16. {sourcecode-1.28.0 → sourcecode-1.30.0}/CONTRIBUTING.md +0 -0
  17. {sourcecode-1.28.0 → sourcecode-1.30.0}/LICENSE +0 -0
  18. {sourcecode-1.28.0 → sourcecode-1.30.0}/SECURITY.md +0 -0
  19. {sourcecode-1.28.0 → sourcecode-1.30.0}/docs/privacy.md +0 -0
  20. {sourcecode-1.28.0 → sourcecode-1.30.0}/docs/schema.md +0 -0
  21. {sourcecode-1.28.0 → sourcecode-1.30.0}/raw +0 -0
  22. {sourcecode-1.28.0 → sourcecode-1.30.0}/run_cli.py +0 -0
  23. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/adaptive_scanner.py +0 -0
  24. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/architecture_analyzer.py +0 -0
  25. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/architecture_summary.py +0 -0
  26. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/ast_extractor.py +0 -0
  27. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/classifier.py +0 -0
  28. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  29. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/confidence_analyzer.py +0 -0
  30. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/context_scorer.py +0 -0
  31. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/context_summarizer.py +0 -0
  32. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/contract_model.py +0 -0
  33. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/contract_pipeline.py +0 -0
  34. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/coverage_parser.py +0 -0
  35. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/dependency_analyzer.py +0 -0
  36. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/__init__.py +0 -0
  37. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/base.py +0 -0
  38. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  39. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/dart.py +0 -0
  40. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/dotnet.py +0 -0
  41. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/elixir.py +0 -0
  42. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/go.py +0 -0
  43. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/heuristic.py +0 -0
  44. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/hybrid.py +0 -0
  45. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/java.py +0 -0
  46. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  47. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/nodejs.py +0 -0
  48. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/parsers.py +0 -0
  49. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/php.py +0 -0
  50. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/project.py +0 -0
  51. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/python.py +0 -0
  52. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/ruby.py +0 -0
  53. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/rust.py +0 -0
  54. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/systems.py +0 -0
  55. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/terraform.py +0 -0
  56. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/detectors/tooling.py +0 -0
  57. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/doc_analyzer.py +0 -0
  58. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  59. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/env_analyzer.py +0 -0
  60. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/git_analyzer.py +0 -0
  62. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/graph_analyzer.py +0 -0
  63. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/metrics_analyzer.py +0 -0
  64. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/progress.py +0 -0
  65. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/ranking_engine.py +0 -0
  66. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/redactor.py +0 -0
  67. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/relevance_scorer.py +0 -0
  68. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/repo_classifier.py +0 -0
  69. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/runtime_classifier.py +0 -0
  70. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/scanner.py +0 -0
  71. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/schema.py +0 -0
  72. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/semantic_analyzer.py +0 -0
  73. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/serializer.py +0 -0
  74. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/summarizer.py +0 -0
  75. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/__init__.py +0 -0
  76. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/config.py +0 -0
  77. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/consent.py +0 -0
  78. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/events.py +0 -0
  79. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/filters.py +0 -0
  80. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/telemetry/transport.py +0 -0
  81. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/tree_utils.py +0 -0
  82. {sourcecode-1.28.0 → sourcecode-1.30.0}/src/sourcecode/workspace.py +0 -0
  83. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/__init__.py +0 -0
  84. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/conftest.py +0 -0
  85. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/coverage.xml +0 -0
  86. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  87. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  88. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  89. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/go_service/go.mod +0 -0
  90. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/jacoco.xml +0 -0
  91. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/latin1_sample.java +0 -0
  92. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/latin1_sample_iso.java +0 -0
  93. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/lcov.info +0 -0
  94. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  95. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/nextjs_app/package.json +0 -0
  96. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  97. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  98. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  99. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  100. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  101. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  102. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  103. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  104. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  105. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  106. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  107. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  108. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  109. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  110. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  111. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  112. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  113. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  114. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  115. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  116. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  117. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  118. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  119. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  120. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  121. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  122. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  123. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  124. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  125. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  126. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  127. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  128. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  129. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_architecture_analyzer.py +0 -0
  130. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_architecture_summary.py +0 -0
  131. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_ast_extractor.py +0 -0
  132. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_block1_reliability.py +0 -0
  133. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_block2_coverage.py +0 -0
  134. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_block5_quality.py +0 -0
  135. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_bug_fixes_v16.py +0 -0
  136. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_classifier.py +0 -0
  137. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_cli.py +0 -0
  138. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_code_notes_analyzer.py +0 -0
  139. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_context_scorer.py +0 -0
  140. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_contract_pipeline.py +0 -0
  141. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_coverage_parser.py +0 -0
  142. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_cross_consistency.py +0 -0
  143. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  144. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  145. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_dependency_schema.py +0 -0
  146. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_dotnet.py +0 -0
  147. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_go_rust_java.py +0 -0
  148. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_nodejs.py +0 -0
  149. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_php_ruby_dart.py +0 -0
  150. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_python.py +0 -0
  151. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_universal_managed.py +0 -0
  152. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detector_universal_systems.py +0 -0
  153. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_detectors_base.py +0 -0
  154. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  155. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_doc_analyzer_python.py +0 -0
  156. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_encoding_regression.py +0 -0
  157. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  158. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_graph_analyzer_python_node.py +0 -0
  159. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_graph_schema.py +0 -0
  160. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_hybrid_inference.py +0 -0
  161. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration.py +0 -0
  162. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_dependencies.py +0 -0
  163. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_detection.py +0 -0
  164. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_docs.py +0 -0
  165. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_graph_modules.py +0 -0
  166. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_lqn.py +0 -0
  167. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_metrics.py +0 -0
  168. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_multistack.py +0 -0
  169. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_semantics.py +0 -0
  170. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_integration_universal.py +0 -0
  171. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_java_spring_integration.py +0 -0
  172. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_metrics_analyzer.py +0 -0
  173. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_packaging.py +0 -0
  174. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_phase1_improvements.py +0 -0
  175. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_pipeline_integrity.py +0 -0
  176. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_real_projects.py +0 -0
  177. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_redactor.py +0 -0
  178. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_scanner.py +0 -0
  179. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_schema.py +0 -0
  180. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_schema_normalization.py +0 -0
  181. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_semantic_analyzer_node.py +0 -0
  182. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_semantic_analyzer_python.py +0 -0
  183. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_semantic_import_resolution.py +0 -0
  184. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_semantic_schema.py +0 -0
  185. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_signal_hierarchy.py +0 -0
  186. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_summarizer.py +0 -0
  187. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_surface_honesty.py +0 -0
  188. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_task_differentiation.py +0 -0
  189. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_telemetry.py +0 -0
  190. {sourcecode-1.28.0 → sourcecode-1.30.0}/tests/test_workspace_analyzer.py +0 -0
@@ -0,0 +1,118 @@
1
+ # Continue Here — atlas-cli session 5
2
+
3
+ **Paused:** 2026-05-16 (sesión 5)
4
+ **Repo:** `/Users/user/Downloads/atlas-cli`
5
+ **Branch:** master
6
+ **Commits esta sesión:** `e5a1a05` + `5c06ef3` (ambos committed, working tree limpio)
7
+
8
+ ---
9
+
10
+ ## Lo que se hizo esta sesión
11
+
12
+ ### Fix 1 — Windows encoding crash en `--since` (`e5a1a05`)
13
+
14
+ **Root cause:** `_get_available_refs()` en `prepare_context.py` llamaba `subprocess.run(text=True)` sin `encoding=` — usaba cp1252 en Windows. Byte 0x8d → `UnicodeDecodeError` no capturado. Además `r.stdout.splitlines()` sin guard → `AttributeError` si stdout fuera None.
15
+
16
+ **Fix:** dos líneas en `prepare_context.py:2309-2315`:
17
+ ```python
18
+ encoding="utf-8", errors="replace", # añadido
19
+ (r.stdout or "").splitlines() # guard añadido
20
+ ```
21
+
22
+ **Tests añadidos:** `tests/test_encoding_regression.py` — clase `TestGetAvailableRefsWindowsEncoding` (4 casos: stdout=None, bytes inválidos, ref inexistente, returncode!=0).
23
+
24
+ ---
25
+
26
+ ### Fix 2 — `review-pr` reescrito como pipeline delta-first (`5c06ef3`)
27
+
28
+ **Problema:** `review-pr` devolvía output genérico de repo completo sin diff real. Inútil para CI/PR review.
29
+
30
+ **Cambios:**
31
+
32
+ | Archivo | Qué cambió |
33
+ |---------|-----------|
34
+ | `prepare_context.py` | Gate 5d: 3 errores tempranos (no_git_repo, no_diff, git_ref_not_found). Reutiliza `_build_delta_impact`. Deriva security_impact/transactional_impact/test_coverage_risk del análisis delta. 7 nuevos campos en TaskOutput. |
35
+ | `cli.py` | Content map review-pr. Bloque output PR-específico. Error JSON + exit(1). Help text. |
36
+ | `test_v1_10_regressions.py` | 3 tests viejos (comportamiento genérico) reemplazados por 4 nuevos (gate git + mocks). |
37
+
38
+ **Nuevos campos en output:**
39
+ ```json
40
+ {
41
+ "review_type": "pull_request",
42
+ "ci_decision": "analysis_success",
43
+ "base_ref": "origin/main",
44
+ "changed_files": [...],
45
+ "affected_modules": [...],
46
+ "security_impact": {"affected_resources": [...], "risk_level": "high"},
47
+ "transactional_impact": {"affected_transactions": [...], "risk": "..."},
48
+ "test_coverage_risk": {"changed_files_without_tests": [...], "risk_level": "medium"},
49
+ "review_hotspots": [...],
50
+ "suggested_review_order": [...]
51
+ }
52
+ ```
53
+
54
+ **GitHub Actions ready:**
55
+ ```yaml
56
+ - run: sourcecode prepare-context review-pr . --since origin/main --output review.json
57
+ # exit 1 si no hay diff o ref no existe
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Estado de tests al pausar
63
+
64
+ ```
65
+ 770 passed, 3 skipped
66
+ ```
67
+
68
+ Fallos pre-existentes (no relacionados, ya presentes antes de esta sesión):
69
+ - `test_block2_coverage.py::test_java_marked_unsupported` — `DocRecord.__init__` bug en `doc_analyzer.py`
70
+ - `test_dependency_analyzer_node_python.py::test_python_requirements_without_lockfile` — typer version mismatch
71
+
72
+ ---
73
+
74
+ ## Pendiente / próximos pasos opcionales
75
+
76
+ 1. **Smoke test review-pr en saint-server real con diff:**
77
+ ```bash
78
+ cd saint-server
79
+ git checkout -b test-review-pr
80
+ # modificar un controller
81
+ sourcecode prepare-context review-pr . --since main --output /tmp/review.json
82
+ cat /tmp/review.json | python3 -c "import json,sys; d=json.load(sys.stdin); print('sections:', list(d.keys()))"
83
+ ```
84
+
85
+ 2. **Verificar `security_impact` detecta correctamente:** cambiar un `SecurityConfig.java` y comprobar que aparece en `security_impact.affected_resources`.
86
+
87
+ 3. **`suggested_review_order` calidad:** verificar que el orden security→controller→service es correcto en PR reales con múltiples tipos de archivos.
88
+
89
+ 4. **`test_coverage_risk` stem matching sin normalización:** el gap entre `generate-tests` (normaliza `TestFoo→Foo`) y `review-pr` (stem directo). Si da falsos "untested", añadir normalización.
90
+
91
+ 5. **Agent token reduction** (trabajo sesiones anteriores): smoke test pendiente en saint-server — ver handoff sesión 4 para comandos.
92
+
93
+ ---
94
+
95
+ ## Para retomar
96
+
97
+ ```bash
98
+ cd /Users/user/Downloads/atlas-cli
99
+ git log --oneline -3
100
+ # debe mostrar:
101
+ # 5c06ef3 (feature) implementando mejoras significativas en prepare-context review-pr...
102
+ # e5a1a05 corrigiendo bug en delta --since en windows
103
+ # f1cb001 corrigiendo bug --agent que duplicaba tokens sin justificación
104
+
105
+ python3 -m pytest tests/ \
106
+ --ignore=tests/test_block2_coverage.py \
107
+ --deselect=tests/test_dependency_analyzer_node_python.py::test_python_requirements_without_lockfile_keeps_declared_versions \
108
+ -q
109
+ # Expected: 770 passed, 3 skipped
110
+
111
+ # Smoke test review-pr
112
+ sourcecode prepare-context review-pr . --since HEAD~1 --output /tmp/review.json
113
+ python3 -c "import json; d=json.load(open('/tmp/review.json')); print(list(d.keys()))"
114
+ ```
115
+
116
+ ---
117
+
118
+ *Pausado 2026-05-16 sesión 5 — gsd:pause-work*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.28.0
3
+ Version: 1.30.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.28.0-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.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.28.0
258
+ # sourcecode 1.30.0
259
259
  ```
260
260
 
261
261
  ---
@@ -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.28.0-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.30.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.28.0
39
+ # sourcecode 1.30.0
40
40
  ```
41
41
 
42
42
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.28.0"
7
+ version = "1.30.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.28.0"
3
+ __version__ = "1.30.0"
@@ -1633,7 +1633,7 @@ def prepare_context_cmd(
1633
1633
  refactor Structural issues, improvement opportunities
1634
1634
  generate-tests Untested source files, test gap analysis
1635
1635
  onboard Full project context for new agents/developers
1636
- review-pr Changed files + architectural impact
1636
+ review-pr PR diff: changed files, security/transactional impact, test gaps (requires git diff or --since)
1637
1637
  delta Incremental context: git-changed files only
1638
1638
 
1639
1639
  \b
@@ -1642,6 +1642,8 @@ def prepare_context_cmd(
1642
1642
  sourcecode prepare-context explain /path/to/repo
1643
1643
  sourcecode prepare-context fix-bug
1644
1644
  sourcecode prepare-context delta --since main
1645
+ sourcecode prepare-context review-pr --since origin/main
1646
+ sourcecode prepare-context review-pr . --since main --output review.json
1645
1647
  sourcecode prepare-context onboard --llm-prompt
1646
1648
  sourcecode prepare-context --task-help
1647
1649
  """
@@ -1734,6 +1736,14 @@ def prepare_context_cmd(
1734
1736
  "test_gaps": False, "code_notes_summary": False,
1735
1737
  "changed_files": True, "affected_entry_points": True,
1736
1738
  },
1739
+ "review-pr": {
1740
+ "project_summary": False, "architecture_summary": False,
1741
+ "relevant_files": True, "key_dependencies": False,
1742
+ "gaps": True, "confidence": False,
1743
+ "suspected_areas": False, "improvement_opportunities": False,
1744
+ "test_gaps": False, "code_notes_summary": False,
1745
+ "changed_files": True, "affected_entry_points": True,
1746
+ },
1737
1747
  }
1738
1748
  _content_filter = _TASK_CONTENT_MAP.get(task, {})
1739
1749
 
@@ -1813,6 +1823,53 @@ def prepare_context_cmd(
1813
1823
  out["dependency_graph_summary"] = output.dependency_graph_summary
1814
1824
  if output.impact_score_per_file:
1815
1825
  out["impact_score_per_file"] = output.impact_score_per_file
1826
+ # review-pr specific fields
1827
+ if task == "review-pr":
1828
+ if output.error_code:
1829
+ _err_out: dict[str, Any] = {
1830
+ "task": output.task,
1831
+ "ci_decision": output.ci_decision or "error",
1832
+ "error": output.error_code,
1833
+ "message": output.error_message,
1834
+ }
1835
+ if output.since:
1836
+ _err_out["since"] = output.since
1837
+ if output.error_hints:
1838
+ _err_out["hint"] = output.error_hints
1839
+ _err_json = json.dumps(_err_out, indent=2, ensure_ascii=False)
1840
+ if output_path is not None:
1841
+ output_path.write_text(_err_json, encoding="utf-8")
1842
+ else:
1843
+ import sys as _sys
1844
+ _sys.stdout.buffer.write(_err_json.encode("utf-8"))
1845
+ _sys.stdout.buffer.write(b"\n")
1846
+ _sys.stdout.buffer.flush()
1847
+ raise typer.Exit(code=1)
1848
+ out["review_type"] = "pull_request"
1849
+ if output.ci_decision:
1850
+ out["ci_decision"] = output.ci_decision
1851
+ if output.base_ref:
1852
+ out["base_ref"] = output.base_ref
1853
+ if output.since:
1854
+ out["since"] = output.since
1855
+ if output.affected_modules:
1856
+ out["affected_modules"] = output.affected_modules
1857
+ if output.security_impact:
1858
+ out["security_impact"] = output.security_impact
1859
+ if output.transactional_impact:
1860
+ out["transactional_impact"] = output.transactional_impact
1861
+ if output.configuration_impact:
1862
+ out["configuration_impact"] = output.configuration_impact
1863
+ if output.test_coverage_risk:
1864
+ out["test_coverage_risk"] = output.test_coverage_risk
1865
+ if output.review_hotspots:
1866
+ out["review_hotspots"] = output.review_hotspots
1867
+ if output.suggested_review_order:
1868
+ out["suggested_review_order"] = output.suggested_review_order
1869
+ if output.impact_summary:
1870
+ out["impact_summary"] = output.impact_summary
1871
+ if output.why_these_files:
1872
+ out["reasoning"] = output.why_these_files
1816
1873
  if output.limitations:
1817
1874
  out["limitations"] = output.limitations
1818
1875
  if output.symptom:
@@ -338,11 +338,19 @@ class TaskOutput:
338
338
  error_message: Optional[str] = None
339
339
  error_hints: list[str] = field(default_factory=list)
340
340
  # CI decision state machine — machine-decidable signal
341
- ci_decision: Optional[str] = None # "no_changes" | "analysis_success" | "git_ref_error"
341
+ ci_decision: Optional[str] = None # "no_changes" | "analysis_success" | "git_ref_error" | "no_git_repo"
342
342
  # git baseline resolution metadata
343
343
  resolved_since_ref: Optional[str] = None # actual ref/hash used for the diff
344
344
  resolution_path: Optional[str] = None # "exact_local_ref"|"remote_tracking_ref"|"symbolic_ref"|"head_minus_1_fallback"|"uncommitted_changes"|"unresolvable"
345
345
  diff_validation_status: Optional[str] = None # "valid_non_empty"|"valid_empty"|"invalid_ref"
346
+ # review-pr specific impact sections
347
+ base_ref: Optional[str] = None
348
+ security_impact: dict = field(default_factory=dict)
349
+ transactional_impact: dict = field(default_factory=dict)
350
+ configuration_impact: dict = field(default_factory=dict)
351
+ test_coverage_risk: dict = field(default_factory=dict)
352
+ review_hotspots: list[str] = field(default_factory=list)
353
+ suggested_review_order: list[str] = field(default_factory=list)
346
354
 
347
355
 
348
356
  # ─────────────────────────────────────────────────────────────────────────────
@@ -659,6 +667,64 @@ class TaskContextBuilder:
659
667
  elif _delta_raw:
660
668
  _delta_files = set(_delta_raw)
661
669
 
670
+ # ── 5d. review-pr: git-first gate ──────────────────────────────────────
671
+ if task_name == "review-pr":
672
+ if not self._is_git_repo():
673
+ return TaskOutput(
674
+ task="review-pr", goal=spec.goal,
675
+ project_summary=None, architecture_summary=None,
676
+ relevant_files=[], suspected_areas=[],
677
+ improvement_opportunities=[], test_gaps=[],
678
+ key_dependencies=[], code_notes_summary=None,
679
+ limitations=[], confidence="low",
680
+ error_code="no_git_repo",
681
+ error_message="review-pr requires a git repository.",
682
+ ci_decision="no_git_repo",
683
+ )
684
+ if since is None:
685
+ # review-pr with no --since: check only uncommitted changes.
686
+ # _get_git_changed_files(since=None) defaults to HEAD~1 which
687
+ # returns the last *committed* diff — a false positive here.
688
+ _pr_raw: Optional[list[str]] = self._get_uncommitted_changed_files()
689
+ else:
690
+ _pr_raw = self._get_git_changed_files(since=since)
691
+ if _pr_raw is None:
692
+ _avail_pr, _sug_pr = self._get_available_refs(since or "")
693
+ _pr_hints: list[str] = []
694
+ if _sug_pr:
695
+ _pr_hints.append(f"Did you mean '{_sug_pr}'?")
696
+ if _avail_pr:
697
+ _pr_hints.append(f"Available refs: {', '.join(_avail_pr[:8])}")
698
+ return TaskOutput(
699
+ task="review-pr", goal=spec.goal,
700
+ project_summary=None, architecture_summary=None,
701
+ relevant_files=[], suspected_areas=[],
702
+ improvement_opportunities=[], test_gaps=[],
703
+ key_dependencies=[], code_notes_summary=None,
704
+ limitations=[], confidence="low",
705
+ since=since,
706
+ error_code="git_ref_not_found",
707
+ error_message=f"Base ref '{since}' not found in this repository.",
708
+ error_hints=_pr_hints,
709
+ gaps=[f"Cannot compute PR diff: git ref '{since}' not found."] + _pr_hints,
710
+ ci_decision="git_ref_error",
711
+ )
712
+ if not _pr_raw:
713
+ _no_diff_hint = "review-pr requires changed files or --since <ref>."
714
+ return TaskOutput(
715
+ task="review-pr", goal=spec.goal,
716
+ project_summary=None, architecture_summary=None,
717
+ relevant_files=[], suspected_areas=[],
718
+ improvement_opportunities=[], test_gaps=[],
719
+ key_dependencies=[], code_notes_summary=None,
720
+ limitations=[], confidence="low",
721
+ error_code="no_diff",
722
+ error_message=f"No PR diff detected. {_no_diff_hint}",
723
+ gaps=[f"No PR diff detected. {_no_diff_hint}"],
724
+ ci_decision="no_changes",
725
+ )
726
+ _delta_files = set(_pr_raw)
727
+
662
728
  # ── 5c. review-pr suspected_areas (needs git uncommitted_files) ──────
663
729
  if task_name == "review-pr" and spec.enable_code_notes:
664
730
  pr_areas: dict[str, int] = {}
@@ -698,7 +764,7 @@ class TaskContextBuilder:
698
764
  _delta_dep_graph_summary: dict = {}
699
765
  _delta_impact_score_per_file: dict = {}
700
766
 
701
- if task_name == "delta":
767
+ if task_name in ("delta", "review-pr"):
702
768
  _delta_changed_list: list[str] = sorted(_delta_files) if _delta_files else []
703
769
  (
704
770
  relevant_files,
@@ -727,7 +793,88 @@ class TaskContextBuilder:
727
793
  delta_files=None,
728
794
  )
729
795
 
730
- # ── 6b. Symptom keyword boost + related notes (fix-bug + --symptom) ──
796
+ # ── 6b. review-pr: derive PR-specific impact sections from delta analysis ──
797
+ _pr_security_impact: dict = {}
798
+ _pr_transactional_impact: dict = {}
799
+ _pr_configuration_impact: dict = {}
800
+ _pr_test_coverage_risk: dict = {}
801
+ _pr_review_hotspots: list[str] = []
802
+ _pr_suggested_review_order: list[str] = []
803
+ _pr_base_ref: Optional[str] = None
804
+
805
+ if task_name == "review-pr":
806
+ _pr_base_ref = since or "HEAD"
807
+ _sys_risk_areas = _delta_system_impact.get("risk_areas", [])
808
+
809
+ _security_files = [
810
+ f for ra in _sys_risk_areas if ra["area"] == "security"
811
+ for f in ra["affected_files"]
812
+ ]
813
+ _transaction_files = [
814
+ f for ra in _sys_risk_areas if ra["area"] in ("transactions", "business_logic")
815
+ for f in ra["affected_files"]
816
+ ]
817
+ _config_files = [
818
+ f for ra in _sys_risk_areas if ra["area"] in ("api", "config")
819
+ for f in ra["affected_files"]
820
+ if any(kw in f.lower() for kw in ("config", "properties", "yml", "yaml", "xml", "spring"))
821
+ ]
822
+
823
+ if _security_files:
824
+ _pr_security_impact = {
825
+ "affected_resources": _security_files,
826
+ "risk_level": "high",
827
+ }
828
+ if _transaction_files:
829
+ _pr_transactional_impact = {
830
+ "affected_transactions": _transaction_files,
831
+ "risk": "possible transaction boundary change",
832
+ }
833
+ if _config_files:
834
+ _pr_configuration_impact = {"changed_configs": _config_files}
835
+
836
+ # Test coverage risk scoped to changed source files only
837
+ _changed_src = [
838
+ f for f in sorted(_delta_files or set())
839
+ if not self._is_test(f) and self._is_source(f)
840
+ ]
841
+ _test_stems = {Path(p).stem for p in test_set}
842
+ _untested_changed = [f for f in _changed_src if Path(f).stem not in _test_stems]
843
+ _test_risk_level = (
844
+ "high" if len(_untested_changed) > 3
845
+ else "medium" if _untested_changed
846
+ else "low"
847
+ )
848
+ _pr_test_coverage_risk = {
849
+ "changed_files_without_tests": _untested_changed[:10],
850
+ "risk_level": _test_risk_level,
851
+ }
852
+
853
+ # Review hotspots: top changed files ranked by impact score
854
+ _pr_review_hotspots = sorted(
855
+ _delta_files or set(),
856
+ key=lambda f: _delta_impact_score_per_file.get(f, 0.0),
857
+ reverse=True,
858
+ )[:8]
859
+
860
+ # Suggested review order: security first, then api → service → persistence → config
861
+ _ORDER_TYPES = ["security", "controller", "service", "repository", "mapper",
862
+ "spring_config", "config", "domain_model", "dto"]
863
+ _seen_order: set[str] = set()
864
+ for _otype in _ORDER_TYPES:
865
+ for _ra in _delta_risk_areas:
866
+ for _f in _ra.get("affected_files", []):
867
+ if _f not in _seen_order:
868
+ _cls = self._classify_changed_file(_f)
869
+ if _cls["artifact_type"] == _otype:
870
+ _pr_suggested_review_order.append(_f)
871
+ _seen_order.add(_f)
872
+ for _f in _pr_review_hotspots:
873
+ if _f not in _seen_order:
874
+ _pr_suggested_review_order.append(_f)
875
+ _seen_order.add(_f)
876
+
877
+ # ── 6c. Symptom keyword boost + related notes (fix-bug + --symptom) ──
731
878
  symptom_keywords: list[str] = []
732
879
  related_notes: list[dict] = []
733
880
  symptom_note: Optional[str] = None
@@ -883,7 +1030,7 @@ class TaskContextBuilder:
883
1030
 
884
1031
  conf_summary, analysis_gaps = ConfidenceAnalyzer().analyze(sm_for_conf)
885
1032
  confidence = conf_summary.overall
886
- if task_name == "delta":
1033
+ if task_name in ("delta", "review-pr"):
887
1034
  # Use delta-specific gaps; ConfidenceAnalyzer gaps are about full-repo
888
1035
  # detection quality and are not meaningful for an incremental diff.
889
1036
  gaps = _delta_analysis_gaps
@@ -895,16 +1042,16 @@ class TaskContextBuilder:
895
1042
  gaps.append(_mybatis_warning["reason"])
896
1043
 
897
1044
  # ── 9. why_these_files ────────────────────────────────────────────────
898
- if task_name == "delta":
1045
+ if task_name in ("delta", "review-pr"):
899
1046
  why_these_files = _delta_why
900
1047
  else:
901
1048
  why_these_files = {rf.path: rf.reason for rf in relevant_files}
902
1049
 
903
- # ── 10. Delta: git changed files + entry points ───────────────────────
1050
+ # ── 10. Delta / review-pr: git changed files + entry points ──────────
904
1051
  changed_files: list[str] = []
905
1052
  affected_entry_points: list[str] = []
906
- if task_name == "delta":
907
- changed_files = sorted(_delta_files) if _delta_files else self._get_git_changed_files(since=since)
1053
+ if task_name in ("delta", "review-pr"):
1054
+ changed_files = sorted(_delta_files) if _delta_files else (self._get_git_changed_files(since=since) or [])
908
1055
  _ep_set = {ep.path for ep in entry_points}
909
1056
  # include framework-detected entry points AND files classified as
910
1057
  # entrypoint/controller/security by artifact taxonomy
@@ -939,16 +1086,24 @@ class TaskContextBuilder:
939
1086
  impact_summary=_delta_impact_summary,
940
1087
  affected_modules=_delta_affected_modules,
941
1088
  risk_areas=_delta_risk_areas,
942
- since=since if task_name == "delta" else None,
1089
+ since=since if task_name in ("delta", "review-pr") else None,
943
1090
  system_impact=_delta_system_impact,
944
1091
  change_type=_delta_change_type,
945
1092
  dependency_graph_summary=_delta_dep_graph_summary,
946
1093
  impact_score_per_file=_delta_impact_score_per_file,
947
1094
  ci_decision=(
948
1095
  "no_changes" if task_name == "delta" and not changed_files
949
- else "analysis_success" if task_name == "delta"
1096
+ else "analysis_success" if task_name in ("delta", "review-pr")
950
1097
  else None
951
1098
  ),
1099
+ # review-pr specific
1100
+ base_ref=_pr_base_ref,
1101
+ security_impact=_pr_security_impact,
1102
+ transactional_impact=_pr_transactional_impact,
1103
+ configuration_impact=_pr_configuration_impact,
1104
+ test_coverage_risk=_pr_test_coverage_risk,
1105
+ review_hotspots=_pr_review_hotspots,
1106
+ suggested_review_order=_pr_suggested_review_order,
952
1107
  )
953
1108
 
954
1109
  def render_prompt(self, output: TaskOutput) -> str:
@@ -1240,6 +1395,19 @@ class TaskContextBuilder:
1240
1395
  def _is_source(self, path: str) -> bool:
1241
1396
  return Path(path).suffix.lower() in _SOURCE_EXTENSIONS
1242
1397
 
1398
+ def _is_git_repo(self) -> bool:
1399
+ import subprocess
1400
+ try:
1401
+ r = subprocess.run(
1402
+ ["git", "rev-parse", "--git-dir"],
1403
+ cwd=str(self.root),
1404
+ capture_output=True, text=True,
1405
+ encoding="utf-8", errors="replace", timeout=5,
1406
+ )
1407
+ return r.returncode == 0
1408
+ except (subprocess.TimeoutExpired, FileNotFoundError):
1409
+ return False
1410
+
1243
1411
  # ── Delta impact analysis ─────────────────────────────────────────────────
1244
1412
 
1245
1413
  @staticmethod
@@ -2248,6 +2416,25 @@ class TaskContextBuilder:
2248
2416
  "error": True,
2249
2417
  }
2250
2418
 
2419
+ def _get_uncommitted_changed_files(self) -> list[str]:
2420
+ """Return files with uncommitted working-tree changes (unstaged only).
2421
+
2422
+ Used by review-pr when no --since ref is given, so we don't confuse
2423
+ the last *committed* diff (HEAD~1 vs HEAD) with an actual PR diff.
2424
+ """
2425
+ import subprocess
2426
+ try:
2427
+ result = subprocess.run(
2428
+ ["git", "diff", "--name-only", "--relative"],
2429
+ cwd=str(self.root), capture_output=True, text=True,
2430
+ encoding="utf-8", errors="replace", timeout=10,
2431
+ )
2432
+ if result.returncode == 0:
2433
+ return [l.strip() for l in (result.stdout or "").splitlines() if l.strip()]
2434
+ except (subprocess.TimeoutExpired, FileNotFoundError):
2435
+ pass
2436
+ return []
2437
+
2251
2438
  def _get_git_changed_files(self, since: Optional[str] = None) -> Optional[list[str]]:
2252
2439
  """Get files changed since a git ref (default: HEAD~1) relative to self.root.
2253
2440
 
@@ -530,27 +530,53 @@ class TestArchitecturePatternHeuristic:
530
530
  # ===========================================================================
531
531
 
532
532
  class TestReviewPrSuspectedAreas:
533
+ """review-pr now requires a git diff — generic fallback removed."""
533
534
 
534
- def test_review_pr_runs_without_error(self):
535
+ def test_review_pr_requires_git_diff(self):
536
+ # FIXTURE has no uncommitted changes (or no git repo) — must exit 1 with structured error.
537
+ # When running inside the atlas-cli git tree with no staged changes, error is "no_diff".
538
+ # When running outside any git repo, error is "no_git_repo". Both are valid.
535
539
  result = _invoke("prepare-context", "review-pr", str(FIXTURE))
536
- assert result.exit_code == 0, result.output
540
+ assert result.exit_code == 1, result.output
541
+ data = _json(result)
542
+ _git_errors = {"no_git_repo", "no_diff", "git_ref_not_found"}
543
+ assert data.get("error") in _git_errors, f"Expected git error, got: {data}"
544
+ assert "ci_decision" in data
537
545
 
538
- def test_review_pr_json_has_suspected_areas_when_files_exist(self):
546
+ def test_review_pr_error_json_is_machine_readable(self):
539
547
  result = _invoke("prepare-context", "review-pr", str(FIXTURE))
540
- assert result.exit_code == 0, result.output
548
+ assert result.exit_code == 1
541
549
  data = _json(result)
542
- # suspected_areas is populated from controller/service/repository adjacency
543
- # Even with no uncommitted files, boosted stems drive the list
544
- assert "suspected_areas" in data, \
545
- f"review-pr must include suspected_areas. Keys: {list(data.keys())}"
546
-
547
- def test_review_pr_suspected_areas_contain_controllers(self):
550
+ assert "error" in data
551
+ assert "message" in data
552
+ assert "ci_decision" in data
553
+
554
+ def test_review_pr_no_diff_error_when_git_but_no_changes(self, monkeypatch):
555
+ # Simulate: is_git_repo=True but no uncommitted changes
556
+ from sourcecode import prepare_context as _pc
557
+ monkeypatch.setattr(_pc.TaskContextBuilder, "_is_git_repo", lambda self: True)
558
+ monkeypatch.setattr(_pc.TaskContextBuilder, "_get_uncommitted_changed_files", lambda self: [])
559
+ result = _invoke("prepare-context", "review-pr", str(FIXTURE))
560
+ assert result.exit_code == 1
561
+ data = _json(result)
562
+ assert data.get("error") == "no_diff"
563
+ assert data.get("ci_decision") == "no_changes"
564
+
565
+ def test_review_pr_with_mocked_diff_returns_pr_fields(self, monkeypatch):
566
+ # Simulate: valid git repo with one changed controller file (no --since → uncommitted path)
567
+ from sourcecode import prepare_context as _pc
568
+ monkeypatch.setattr(_pc.TaskContextBuilder, "_is_git_repo", lambda self: True)
569
+ monkeypatch.setattr(
570
+ _pc.TaskContextBuilder, "_get_uncommitted_changed_files",
571
+ lambda self: ["src/main/java/com/example/UserController.java"],
572
+ )
548
573
  result = _invoke("prepare-context", "review-pr", str(FIXTURE))
549
574
  assert result.exit_code == 0, result.output
550
575
  data = _json(result)
551
- areas = data.get("suspected_areas", [])
552
- has_controller = any("Controller" in a or "Service" in a or "Mapper" in a for a in areas)
553
- assert has_controller, f"Expected controller/service/mapper in suspected_areas: {areas}"
576
+ assert data.get("review_type") == "pull_request"
577
+ assert "changed_files" in data
578
+ assert "test_coverage_risk" in data
579
+ assert data.get("ci_decision") == "analysis_success"
554
580
 
555
581
  def test_fix_bug_still_uses_annotation_density(self):
556
582
  result = _invoke("prepare-context", "fix-bug", str(FIXTURE))