sourcecode 1.30.27__tar.gz → 1.30.28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. {sourcecode-1.30.27 → sourcecode-1.30.28}/PKG-INFO +3 -3
  2. {sourcecode-1.30.27 → sourcecode-1.30.28}/README.md +2 -2
  3. {sourcecode-1.30.27 → sourcecode-1.30.28}/pyproject.toml +1 -1
  4. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/cli.py +19 -5
  6. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/prepare_context.py +30 -3
  7. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/serializer.py +10 -0
  8. sourcecode-1.30.28/tests/test_bug_fixes_v1302.py +419 -0
  9. {sourcecode-1.30.27 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  10. {sourcecode-1.30.27 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  11. {sourcecode-1.30.27 → sourcecode-1.30.28}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  12. {sourcecode-1.30.27 → sourcecode-1.30.28}/.continue-here.md +0 -0
  13. {sourcecode-1.30.27 → sourcecode-1.30.28}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-1.30.27 → sourcecode-1.30.28}/.gitignore +0 -0
  15. {sourcecode-1.30.27 → sourcecode-1.30.28}/.ruff.toml +0 -0
  16. {sourcecode-1.30.27 → sourcecode-1.30.28}/CONTRIBUTING.md +0 -0
  17. {sourcecode-1.30.27 → sourcecode-1.30.28}/LICENSE +0 -0
  18. {sourcecode-1.30.27 → sourcecode-1.30.28}/SECURITY.md +0 -0
  19. {sourcecode-1.30.27 → sourcecode-1.30.28}/docs/privacy.md +0 -0
  20. {sourcecode-1.30.27 → sourcecode-1.30.28}/docs/schema.md +0 -0
  21. {sourcecode-1.30.27 → sourcecode-1.30.28}/raw +0 -0
  22. {sourcecode-1.30.27 → sourcecode-1.30.28}/run_cli.py +0 -0
  23. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/adaptive_scanner.py +0 -0
  24. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/architecture_analyzer.py +0 -0
  25. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/architecture_summary.py +0 -0
  26. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/ast_extractor.py +0 -0
  27. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/classifier.py +0 -0
  28. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/code_notes_analyzer.py +0 -0
  29. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/confidence_analyzer.py +0 -0
  30. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/context_scorer.py +0 -0
  31. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/context_summarizer.py +0 -0
  32. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/contract_model.py +0 -0
  33. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/contract_pipeline.py +0 -0
  34. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/coverage_parser.py +0 -0
  35. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/dependency_analyzer.py +0 -0
  36. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/__init__.py +0 -0
  37. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/base.py +0 -0
  38. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/csproj_parser.py +0 -0
  39. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/dart.py +0 -0
  40. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/dotnet.py +0 -0
  41. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/elixir.py +0 -0
  42. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/go.py +0 -0
  43. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/heuristic.py +0 -0
  44. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/hybrid.py +0 -0
  45. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/java.py +0 -0
  46. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/jvm_ext.py +0 -0
  47. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/nodejs.py +0 -0
  48. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/parsers.py +0 -0
  49. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/php.py +0 -0
  50. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/project.py +0 -0
  51. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/python.py +0 -0
  52. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/ruby.py +0 -0
  53. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/rust.py +0 -0
  54. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/systems.py +0 -0
  55. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/terraform.py +0 -0
  56. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/detectors/tooling.py +0 -0
  57. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/doc_analyzer.py +0 -0
  58. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/entrypoint_classifier.py +0 -0
  59. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/env_analyzer.py +0 -0
  60. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/flow_analyzer.py +0 -0
  62. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/git_analyzer.py +0 -0
  63. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/graph_analyzer.py +0 -0
  64. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/metrics_analyzer.py +0 -0
  65. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/pr_comment_renderer.py +0 -0
  66. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/progress.py +0 -0
  67. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/ranking_engine.py +0 -0
  68. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/redactor.py +0 -0
  69. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/relevance_scorer.py +0 -0
  70. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/repo_classifier.py +0 -0
  71. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/repository_ir.py +0 -0
  72. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/runtime_classifier.py +0 -0
  73. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/scanner.py +0 -0
  74. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/schema.py +0 -0
  75. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/semantic_analyzer.py +0 -0
  76. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/summarizer.py +0 -0
  77. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/__init__.py +0 -0
  78. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/config.py +0 -0
  79. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/consent.py +0 -0
  80. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/events.py +0 -0
  81. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/filters.py +0 -0
  82. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/telemetry/transport.py +0 -0
  83. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/tree_utils.py +0 -0
  84. {sourcecode-1.30.27 → sourcecode-1.30.28}/src/sourcecode/workspace.py +0 -0
  85. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/__init__.py +0 -0
  86. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/conftest.py +0 -0
  87. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/coverage.xml +0 -0
  88. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  89. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/fastapi_app/src/main.py +0 -0
  90. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  91. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/go_service/go.mod +0 -0
  92. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/jacoco.xml +0 -0
  93. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/latin1_sample.java +0 -0
  94. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/latin1_sample_iso.java +0 -0
  95. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/lcov.info +0 -0
  96. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  97. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/package.json +0 -0
  98. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  99. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  100. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  101. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  102. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  103. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  104. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  105. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  106. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  107. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  108. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  109. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  110. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  111. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  112. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  113. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  114. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  115. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  116. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  117. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  118. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  119. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  120. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  121. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  122. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  123. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  124. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  125. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  126. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  127. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  128. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  129. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  130. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  131. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_architecture_analyzer.py +0 -0
  132. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_architecture_summary.py +0 -0
  133. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_ast_extractor.py +0 -0
  134. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_block1_reliability.py +0 -0
  135. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_block2_coverage.py +0 -0
  136. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_block5_quality.py +0 -0
  137. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_bug_fixes_v16.py +0 -0
  138. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_classifier.py +0 -0
  139. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_cli.py +0 -0
  140. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_code_notes_analyzer.py +0 -0
  141. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_context_scorer.py +0 -0
  142. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_contract_pipeline.py +0 -0
  143. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_coverage_parser.py +0 -0
  144. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_cross_consistency.py +0 -0
  145. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_dependency_analyzer_node_python.py +0 -0
  146. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_dependency_analyzer_polyglot.py +0 -0
  147. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_dependency_schema.py +0 -0
  148. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_dotnet.py +0 -0
  149. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_go_rust_java.py +0 -0
  150. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_nodejs.py +0 -0
  151. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_php_ruby_dart.py +0 -0
  152. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_python.py +0 -0
  153. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_universal_managed.py +0 -0
  154. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detector_universal_systems.py +0 -0
  155. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_detectors_base.py +0 -0
  156. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_doc_analyzer_jsdom.py +0 -0
  157. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_doc_analyzer_python.py +0 -0
  158. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_encoding_regression.py +0 -0
  159. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_graph_analyzer_polyglot.py +0 -0
  160. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_graph_analyzer_python_node.py +0 -0
  161. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_graph_schema.py +0 -0
  162. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_hybrid_inference.py +0 -0
  163. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration.py +0 -0
  164. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_dependencies.py +0 -0
  165. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_detection.py +0 -0
  166. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_docs.py +0 -0
  167. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_graph_modules.py +0 -0
  168. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_lqn.py +0 -0
  169. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_metrics.py +0 -0
  170. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_multistack.py +0 -0
  171. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_semantics.py +0 -0
  172. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_integration_universal.py +0 -0
  173. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_java_spring_integration.py +0 -0
  174. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_metrics_analyzer.py +0 -0
  175. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_output_ux.py +0 -0
  176. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_packaging.py +0 -0
  177. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_phase1_improvements.py +0 -0
  178. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_pipeline_integrity.py +0 -0
  179. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_real_projects.py +0 -0
  180. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_redactor.py +0 -0
  181. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_repository_ir.py +0 -0
  182. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_scanner.py +0 -0
  183. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_schema.py +0 -0
  184. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_schema_normalization.py +0 -0
  185. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_scoring_calibration.py +0 -0
  186. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_semantic_analyzer_node.py +0 -0
  187. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_semantic_analyzer_python.py +0 -0
  188. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_semantic_import_resolution.py +0 -0
  189. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_semantic_schema.py +0 -0
  190. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_signal_hierarchy.py +0 -0
  191. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_summarizer.py +0 -0
  192. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_surface_honesty.py +0 -0
  193. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_task_differentiation.py +0 -0
  194. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_telemetry.py +0 -0
  195. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_v131_improvements.py +0 -0
  196. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_v1_10_regressions.py +0 -0
  197. {sourcecode-1.30.27 → sourcecode-1.30.28}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.30.27
3
+ Version: 1.30.28
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
221
221
 
222
222
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
223
223
 
224
- ![Version](https://img.shields.io/badge/version-1.30.27-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.28-blue)
225
225
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
226
226
 
227
227
  ---
@@ -257,7 +257,7 @@ pipx install sourcecode
257
257
 
258
258
  ```bash
259
259
  sourcecode version
260
- # sourcecode 1.30.27
260
+ # sourcecode 1.30.28
261
261
  ```
262
262
 
263
263
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.30.27-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.30.28-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -38,7 +38,7 @@ pipx install sourcecode
38
38
 
39
39
  ```bash
40
40
  sourcecode version
41
- # sourcecode 1.30.27
41
+ # sourcecode 1.30.28
42
42
  ```
43
43
 
44
44
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.30.27"
7
+ version = "1.30.28"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.30.27"
3
+ __version__ = "1.30.28"
@@ -424,7 +424,7 @@ def main(
424
424
  no_redact: bool = typer.Option(
425
425
  False,
426
426
  "--no-redact",
427
- help="Disable secret redaction. Use with caution output may contain sensitive values.",
427
+ help="Disable secret redaction of output strings. Note: env var values from the OS are never included in output regardless of this flag (security policy).",
428
428
  ),
429
429
  version: Optional[bool] = typer.Option(
430
430
  None,
@@ -437,7 +437,7 @@ def main(
437
437
  depth: int = typer.Option(
438
438
  4,
439
439
  "--depth",
440
- help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to 12.",
440
+ help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to a minimum of 12; values below 12 have no effect on Java projects.",
441
441
  min=1,
442
442
  max=20,
443
443
  ),
@@ -1734,6 +1734,11 @@ def prepare_context_cmd(
1734
1734
  help="Emit per-phase timing to stderr (git scan ms, symptom scoring ms, total ms)",
1735
1735
  hidden=True,
1736
1736
  ),
1737
+ fast: bool = typer.Option(
1738
+ False,
1739
+ "--fast",
1740
+ help="Skip deep analysis (content search, test gap discovery, code annotations). Uses manifest/metadata only. Target: < 6 s.",
1741
+ ),
1737
1742
  ) -> None:
1738
1743
  """Task-specific context for AI coding agents.
1739
1744
 
@@ -1808,9 +1813,14 @@ def prepare_context_cmd(
1808
1813
  if since:
1809
1814
  _phase += f" since {since}"
1810
1815
  _progress.start(_phase)
1816
+ if not fast:
1817
+ import sys as _sys
1818
+ if _sys.stderr.isatty():
1819
+ _sys.stderr.write(f"Analyzing ({task})... (deep scan may take 15–35 s for large codebases)\n")
1820
+ _sys.stderr.flush()
1811
1821
  _t0 = _time.perf_counter()
1812
1822
  try:
1813
- output = builder.build(task, since=since, symptom=symptom)
1823
+ output = builder.build(task, since=since, symptom=symptom, fast=fast)
1814
1824
  finally:
1815
1825
  _progress.finish()
1816
1826
  _t_total = (_time.perf_counter() - _t0) * 1000
@@ -1960,8 +1970,8 @@ def prepare_context_cmd(
1960
1970
  _sys.stdout.buffer.write(b"\n")
1961
1971
  _sys.stdout.buffer.flush()
1962
1972
  if copy:
1963
- _copy_to_clipboard(_nc_json)
1964
- typer.echo("✓ copied to clipboard", err=True)
1973
+ if _copy_to_clipboard(_nc_json):
1974
+ typer.echo("✓ copied to clipboard", err=True)
1965
1975
  raise typer.Exit()
1966
1976
  if output.ci_decision:
1967
1977
  out["ci_decision"] = output.ci_decision
@@ -2082,6 +2092,10 @@ def prepare_context_cmd(
2082
2092
  out["symptom_note"] = output.symptom_note
2083
2093
  if output.symptom_explain:
2084
2094
  out["symptom_explain"] = output.symptom_explain
2095
+ if getattr(output, "symptom_hint", None):
2096
+ out["symptom_hint"] = output.symptom_hint
2097
+ if getattr(output, "warnings", None):
2098
+ out["warnings"] = output.warnings
2085
2099
  if llm_prompt:
2086
2100
  out["llm_prompt"] = builder.render_prompt(output)
2087
2101
 
@@ -335,6 +335,7 @@ class TaskOutput:
335
335
  related_notes: list[dict] = field(default_factory=list) # fix-bug + symptom only
336
336
  symptom_note: Optional[str] = None # fix-bug: cross-layer synonym note
337
337
  symptom_explain: Optional[dict] = None # fix-bug: structured evidence breakdown
338
+ symptom_hint: Optional[str] = None # fix-bug: redirect hint when term not found in this module
338
339
  # delta-specific impact fields
339
340
  impact_summary: Optional[str] = None
340
341
  affected_modules: list[str] = field(default_factory=list)
@@ -348,6 +349,7 @@ class TaskOutput:
348
349
  error_code: Optional[str] = None
349
350
  error_message: Optional[str] = None
350
351
  error_hints: list[str] = field(default_factory=list)
352
+ warnings: list[dict] = field(default_factory=list) # structured warnings (REF_NOT_FOUND, etc.)
351
353
  # CI decision state machine — machine-decidable signal
352
354
  ci_decision: Optional[str] = None # "no_changes" | "analysis_success" | "git_ref_error" | "no_git_repo"
353
355
  # git baseline resolution metadata
@@ -683,7 +685,7 @@ class TaskContextBuilder:
683
685
  def __init__(self, root: Path) -> None:
684
686
  self.root = root
685
687
 
686
- def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None) -> TaskOutput:
688
+ def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False) -> TaskOutput:
687
689
  if task_name not in TASKS:
688
690
  raise ValueError(
689
691
  f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
@@ -880,7 +882,7 @@ class TaskContextBuilder:
880
882
  improvement_opportunities: list[str] = []
881
883
  cn_notes_for_ranking: list = []
882
884
 
883
- if spec.enable_code_notes:
885
+ if spec.enable_code_notes and not fast:
884
886
  from dataclasses import asdict
885
887
  from sourcecode.code_notes_analyzer import CodeNotesAnalyzer
886
888
 
@@ -1317,6 +1319,7 @@ class TaskContextBuilder:
1317
1319
  related_notes: list[dict] = []
1318
1320
  symptom_note: Optional[str] = None
1319
1321
  symptom_explain: Optional[dict] = None
1322
+ symptom_hint: Optional[str] = None
1320
1323
  if task_name == "fix-bug" and symptom:
1321
1324
  import re as _re
1322
1325
  _camel_expanded = _re.sub(r'([a-z])([A-Z])', r'\1 \2', symptom)
@@ -1537,9 +1540,30 @@ class TaskContextBuilder:
1537
1540
  ),
1538
1541
  }
1539
1542
 
1543
+ # BUG #4: LOW confidence + 0 content matches → clear suspected_areas,
1544
+ # emit actionable redirect instead of unrelated files.
1545
+ if _sx_confidence == "LOW" and not _sx_content:
1546
+ suspected_areas = []
1547
+ _is_fe_term = any(kw in _FRONTEND_SYMPTOM_MAP for kw in symptom_keywords)
1548
+ _root_name = self.root.name
1549
+ if _is_fe_term:
1550
+ _fe_redirect = (
1551
+ f"Term {symptom!r} not found in sources under {_root_name!r}. "
1552
+ f"This appears to be a frontend symptom. "
1553
+ f"Try: prepare-context fix-bug . --symptom {symptom!r} "
1554
+ f"(monorepo root) or target a frontend sub-project directly."
1555
+ )
1556
+ else:
1557
+ _fe_redirect = (
1558
+ f"Term {symptom!r} not found in sources under {_root_name!r}. "
1559
+ f"Verify the spelling or try a related term. "
1560
+ f"If this is a frontend symptom, run against the frontend sub-project."
1561
+ )
1562
+ symptom_hint = _fe_redirect
1563
+
1540
1564
  # ── 7. Test gaps (generate-tests only) ────────────────────────────
1541
1565
  test_gaps: list[str] = []
1542
- if task_name == "generate-tests":
1566
+ if task_name == "generate-tests" and not fast:
1543
1567
  def _normalize_test_stem(stem: str) -> str:
1544
1568
  # Java: FooTest / FooTests → Foo; TestFoo → Foo
1545
1569
  if stem.endswith("Tests"):
@@ -1683,6 +1707,8 @@ class TaskContextBuilder:
1683
1707
  resolved_since_ref=_delta_baseline.get("resolved_ref") if task_name == "delta" else None,
1684
1708
  resolution_path=_delta_baseline.get("resolution_path") if task_name == "delta" else None,
1685
1709
  diff_validation_status=_delta_baseline.get("diff_validation_status") if task_name == "delta" else None,
1710
+ warnings=_delta_baseline.get("warnings", []) if task_name == "delta" else [],
1711
+ symptom_hint=symptom_hint if task_name == "fix-bug" else None,
1686
1712
  )
1687
1713
 
1688
1714
  def render_prompt(self, output: TaskOutput) -> str:
@@ -3261,6 +3287,7 @@ class TaskContextBuilder:
3261
3287
  "resolution_path": "head_minus_1_fallback",
3262
3288
  "diff_validation_status": "invalid_ref", # original ref unresolved
3263
3289
  "error": False,
3290
+ "warnings": [{"code": "REF_NOT_FOUND", "ref": since, "resolved_to": "HEAD~1"}],
3264
3291
  }
3265
3292
 
3266
3293
  # All stages failed
@@ -248,6 +248,16 @@ def _compact_git_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
248
248
  for f, n in _fc.most_common(5)
249
249
  ]
250
250
  ctx["hotspots_source"] = "recent_commits"
251
+ if gc.recent_commits:
252
+ ctx["recent_commits"] = [
253
+ {
254
+ "hash": c.hash[:8],
255
+ "message": (c.message or "")[:80],
256
+ "date": (c.date or "")[:10],
257
+ "author": c.author or "",
258
+ }
259
+ for c in gc.recent_commits[:5]
260
+ ]
251
261
  return ctx if ctx else None
252
262
 
253
263
 
@@ -0,0 +1,419 @@
1
+ """Tests for v1.30.27 bug fixes and improvements.
2
+
3
+ BUG #1 --copy confirmation goes to stderr (not stdout)
4
+ BUG #2 --since non-existent branch emits warnings[] in output
5
+ BUG #3 --depth help text documents Java auto-adjust behavior
6
+ BUG #4 symptom LOW-confidence: suspected_areas cleared, symptom_hint present
7
+ IMP #5 --fast flag skips deep analysis; progress message gated on TTY
8
+ IMP #6 git_context compact output includes recent_commits
9
+ IMP #7 --no-redact help text documents security policy
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+ from typing import Any
16
+ from unittest.mock import patch, MagicMock
17
+
18
+ import pytest
19
+ from typer.testing import CliRunner
20
+
21
+ from sourcecode.cli import app, _copy_to_clipboard
22
+ from sourcecode import prepare_context as _pc
23
+ from sourcecode.prepare_context import TaskContextBuilder, TaskOutput
24
+ from sourcecode.schema import GitContext, CommitRecord, ChangeHotspot, UncommittedChanges, SourceMap, AnalysisMetadata
25
+ from sourcecode.serializer import _compact_git_context
26
+
27
+ runner = CliRunner()
28
+
29
+ FIXTURE = Path(__file__).parent / "fixtures" / "fastapi_app"
30
+
31
+
32
+ def _invoke(*args: str):
33
+ return runner.invoke(app, list(args))
34
+
35
+
36
+ def _json(result) -> dict:
37
+ try:
38
+ return json.loads(result.output)
39
+ except json.JSONDecodeError as e:
40
+ raise AssertionError(f"Not valid JSON. output={result.output!r}") from e
41
+
42
+
43
+ # ── BUG #1: --copy confirmation must go to stderr ────────────────────────────
44
+
45
+ class TestCopyToStderr:
46
+ def test_copy_confirmation_uses_err_flag(self, tmp_path):
47
+ """typer.echo for clipboard confirmation uses err=True (routes to stderr).
48
+
49
+ CliRunner mixes stderr into result.output, so we verify the JSON is
50
+ parseable (as the leading content) and the message appears after the JSON.
51
+ In real piped usage, stdout only carries JSON; stderr carries the message.
52
+ """
53
+ (tmp_path / "main.py").write_text("x = 1\n")
54
+ with patch("sourcecode.cli._copy_to_clipboard", return_value=True):
55
+ result = runner.invoke(app, [str(tmp_path), "--compact", "--copy"])
56
+ assert result.exit_code == 0
57
+ # The JSON portion at the start must be parseable via raw_decode
58
+ decoder = json.JSONDecoder()
59
+ data, end_idx = decoder.raw_decode(result.output.lstrip())
60
+ assert isinstance(data, dict)
61
+ # Confirmation must appear AFTER the JSON (proves it was stderr, not inline)
62
+ trailing = result.output[end_idx:].strip()
63
+ assert "copied to clipboard" in trailing, (
64
+ "Confirmation must be in stderr (appears after JSON in CliRunner output)"
65
+ )
66
+
67
+ def test_copy_failure_no_message(self, tmp_path):
68
+ """No confirmation when clipboard copy fails."""
69
+ (tmp_path / "main.py").write_text("x = 1\n")
70
+ with patch("sourcecode.cli._copy_to_clipboard", return_value=False):
71
+ result = runner.invoke(app, [str(tmp_path), "--compact", "--copy"])
72
+ assert result.exit_code == 0
73
+ assert "copied to clipboard" not in result.output
74
+
75
+ def test_prepare_context_copy_conditional_on_success(self, tmp_path):
76
+ """prepare-context --copy message only shown when clipboard succeeds."""
77
+ (tmp_path / "main.py").write_text("x = 1\n")
78
+ with patch("sourcecode.cli._copy_to_clipboard", return_value=False):
79
+ result = runner.invoke(
80
+ app, ["prepare-context", "explain", str(tmp_path), "--copy"]
81
+ )
82
+ # No "copied to clipboard" in any output when copy fails
83
+ assert "copied to clipboard" not in result.output
84
+
85
+
86
+ # ── BUG #2: --since non-existent ref emits warnings[] ────────────────────────
87
+
88
+ class TestSinceWarnings:
89
+ def test_head_minus1_fallback_emits_warning(self, tmp_path):
90
+ """Stage 4 fallback populates warnings[] with REF_NOT_FOUND."""
91
+ builder = TaskContextBuilder(tmp_path)
92
+
93
+ # Simulate Stage 4: ref invalid, HEAD~1 available
94
+ def _mock_resolve(since):
95
+ return {
96
+ "files": ["main.py"],
97
+ "resolved_ref": "HEAD~1",
98
+ "resolution_path": "head_minus_1_fallback",
99
+ "diff_validation_status": "invalid_ref",
100
+ "error": False,
101
+ "warnings": [{"code": "REF_NOT_FOUND", "ref": since, "resolved_to": "HEAD~1"}],
102
+ }
103
+
104
+ (tmp_path / "main.py").write_text("x = 1\n")
105
+ with patch.object(builder, "_resolve_git_baseline", side_effect=_mock_resolve):
106
+ with patch.object(builder, "_build_delta_impact") as mock_impact:
107
+ mock_impact.return_value = MagicMock(
108
+ relevant_files=[], impact_summary=None, affected_modules=[],
109
+ risk_areas=[], why_these_files={}, analysis_gaps=[],
110
+ system_impact={}, change_type=[], dependency_graph_summary={},
111
+ impact_score_per_file={},
112
+ )
113
+ with patch.object(builder, "_get_pr_scope_files", return_value=(None, "git_diff", [], [])):
114
+ output = builder.build("delta", since="main")
115
+
116
+ assert output.warnings, "warnings must be non-empty for Stage 4 fallback"
117
+ assert output.warnings[0]["code"] == "REF_NOT_FOUND"
118
+ assert output.warnings[0]["ref"] == "main"
119
+ assert output.warnings[0]["resolved_to"] == "HEAD~1"
120
+
121
+ def test_valid_ref_no_warnings(self, tmp_path):
122
+ """Exact ref resolution (Stage 1) produces no warnings."""
123
+ builder = TaskContextBuilder(tmp_path)
124
+
125
+ def _mock_resolve(since):
126
+ return {
127
+ "files": [],
128
+ "resolved_ref": since,
129
+ "resolution_path": "exact_local_ref",
130
+ "diff_validation_status": "valid_empty",
131
+ "error": False,
132
+ }
133
+
134
+ with patch.object(builder, "_resolve_git_baseline", side_effect=_mock_resolve):
135
+ with patch.object(builder, "_build_delta_impact") as mock_impact:
136
+ mock_impact.return_value = MagicMock(
137
+ relevant_files=[], impact_summary=None, affected_modules=[],
138
+ risk_areas=[], why_these_files={}, analysis_gaps=[],
139
+ system_impact={}, change_type=[], dependency_graph_summary={},
140
+ impact_score_per_file={},
141
+ )
142
+ output = builder.build("delta", since="origin/develop")
143
+
144
+ assert not output.warnings
145
+
146
+ def test_warnings_serialized_in_cli_output(self, tmp_path):
147
+ """warnings[] appears in JSON output when populated."""
148
+ (tmp_path / "main.py").write_text("x = 1\n")
149
+
150
+ mock_output = MagicMock(spec=TaskOutput)
151
+ mock_output.task = "delta"
152
+ mock_output.goal = "g"
153
+ mock_output.project_summary = None
154
+ mock_output.architecture_summary = None
155
+ mock_output.relevant_files = []
156
+ mock_output.suspected_areas = []
157
+ mock_output.improvement_opportunities = []
158
+ mock_output.test_gaps = []
159
+ mock_output.key_dependencies = []
160
+ mock_output.code_notes_summary = None
161
+ mock_output.limitations = []
162
+ mock_output.confidence = "low"
163
+ mock_output.gaps = []
164
+ mock_output.why_these_files = {}
165
+ mock_output.changed_files = []
166
+ mock_output.affected_entry_points = []
167
+ mock_output.symptom = None
168
+ mock_output.related_notes = []
169
+ mock_output.symptom_note = None
170
+ mock_output.symptom_explain = None
171
+ mock_output.symptom_hint = None
172
+ mock_output.impact_summary = None
173
+ mock_output.affected_modules = []
174
+ mock_output.risk_areas = []
175
+ mock_output.since = "main"
176
+ mock_output.system_impact = {}
177
+ mock_output.change_type = []
178
+ mock_output.dependency_graph_summary = {}
179
+ mock_output.impact_score_per_file = {}
180
+ mock_output.error_code = None
181
+ mock_output.error_message = None
182
+ mock_output.error_hints = []
183
+ mock_output.ci_decision = "analysis_success"
184
+ mock_output.resolved_since_ref = "HEAD~1"
185
+ mock_output.resolution_path = "head_minus_1_fallback"
186
+ mock_output.diff_validation_status = "invalid_ref"
187
+ mock_output.warnings = [{"code": "REF_NOT_FOUND", "ref": "main", "resolved_to": "HEAD~1"}]
188
+ mock_output.base_ref = None
189
+ mock_output.security_impact = {}
190
+ mock_output.transactional_impact = {}
191
+ mock_output.configuration_impact = {}
192
+ mock_output.test_coverage_risk = {}
193
+ mock_output.review_hotspots = []
194
+ mock_output.suggested_review_order = []
195
+ mock_output.execution_paths = []
196
+ mock_output.behavioral_impact = []
197
+ mock_output.scope_source = None
198
+ mock_output.scope_files = []
199
+ mock_output.repo_root = None
200
+ mock_output.runtime_changes = []
201
+ mock_output.build_changes = {}
202
+ mock_output.committed_changes = []
203
+ mock_output.uncommitted_changes = []
204
+ mock_output.analysis_scope = {}
205
+
206
+ with patch.object(TaskContextBuilder, "build", return_value=mock_output):
207
+ result = runner.invoke(
208
+ app, ["prepare-context", "delta", str(tmp_path), "--since", "main"]
209
+ )
210
+
211
+ assert result.exit_code == 0
212
+ data = _json(result)
213
+ assert "warnings" in data
214
+ assert data["warnings"][0]["code"] == "REF_NOT_FOUND"
215
+
216
+
217
+ # ── BUG #3: --depth help text ─────────────────────────────────────────────────
218
+
219
+ class TestDepthHelp:
220
+ def test_depth_help_documents_java_autoadjust(self):
221
+ result = runner.invoke(app, ["--help"])
222
+ assert result.exit_code == 0
223
+ assert "12" in result.output # Java min depth mentioned
224
+ # The help text should mention auto-adjust or Java
225
+ assert "Java" in result.output or "auto" in result.output.lower()
226
+
227
+ def test_depth_flag_accepted(self, tmp_path):
228
+ (tmp_path / "main.py").write_text("x = 1\n")
229
+ result = runner.invoke(app, [str(tmp_path), "--depth", "6", "--compact"])
230
+ assert result.exit_code == 0
231
+
232
+
233
+ # ── BUG #4: symptom LOW-confidence clears suspected_areas, adds symptom_hint ──
234
+
235
+ class TestSymptomHint:
236
+ def _make_java_fixture(self, tmp_path: Path) -> Path:
237
+ src = tmp_path / "src" / "main" / "java" / "com" / "example"
238
+ src.mkdir(parents=True)
239
+ (src / "UserService.java").write_text(
240
+ "package com.example;\npublic class UserService {}\n"
241
+ )
242
+ (tmp_path / "pom.xml").write_text(
243
+ "<project><groupId>com.example</groupId></project>\n"
244
+ )
245
+ return tmp_path
246
+
247
+ def test_frontend_symptom_in_java_module_clears_suspected_areas(self, tmp_path):
248
+ """'spinner' in Java module → suspected_areas=[], symptom_hint present."""
249
+ self._make_java_fixture(tmp_path)
250
+ result = runner.invoke(
251
+ app,
252
+ ["prepare-context", "fix-bug", str(tmp_path), "--symptom", "spinner"],
253
+ )
254
+ assert result.exit_code == 0
255
+ data = _json(result)
256
+ # suspected_areas must be empty when confidence is LOW with 0 content matches
257
+ if data.get("symptom_explain", {}).get("confidence") == "LOW":
258
+ assert data.get("suspected_areas", []) == [], (
259
+ "suspected_areas must be [] when symptom confidence is LOW"
260
+ )
261
+ assert "symptom_hint" in data, "symptom_hint must be present when LOW confidence"
262
+ assert "spinner" in data["symptom_hint"].lower() or "frontend" in data["symptom_hint"].lower()
263
+
264
+ def test_java_symptom_no_hint(self, tmp_path):
265
+ """Java term in Java module → no symptom_hint injected."""
266
+ self._make_java_fixture(tmp_path)
267
+ result = runner.invoke(
268
+ app,
269
+ ["prepare-context", "fix-bug", str(tmp_path), "--symptom", "UserService"],
270
+ )
271
+ assert result.exit_code == 0
272
+ data = _json(result)
273
+ # If confidence is not LOW, no symptom_hint should be present
274
+ sx = data.get("symptom_explain", {})
275
+ if sx.get("confidence") != "LOW":
276
+ assert "symptom_hint" not in data
277
+
278
+ def test_symptom_hint_field_on_taskoutput(self):
279
+ """TaskOutput dataclass has symptom_hint field."""
280
+ out = TaskOutput(
281
+ task="fix-bug", goal="g", project_summary=None,
282
+ architecture_summary=None, relevant_files=[], suspected_areas=[],
283
+ improvement_opportunities=[], test_gaps=[], key_dependencies=[],
284
+ code_notes_summary=None, limitations=[], symptom_hint="redirect hint",
285
+ )
286
+ assert out.symptom_hint == "redirect hint"
287
+
288
+
289
+ # ── IMPROVEMENT #5: --fast flag ───────────────────────────────────────────────
290
+
291
+ class TestFastFlag:
292
+ def test_fast_flag_accepted(self, tmp_path):
293
+ """--fast flag accepted without error."""
294
+ (tmp_path / "main.py").write_text("x = 1\n")
295
+ result = runner.invoke(
296
+ app, ["prepare-context", "explain", str(tmp_path), "--fast"]
297
+ )
298
+ assert result.exit_code == 0
299
+ data = _json(result)
300
+ assert "task" in data
301
+
302
+ def test_fast_skips_code_notes(self, tmp_path):
303
+ """--fast produces output without triggering code notes analysis."""
304
+ (tmp_path / "main.py").write_text("# TODO: fix this\nx = 1\n")
305
+ (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n')
306
+
307
+ called = []
308
+ orig_analyze = None
309
+ try:
310
+ from sourcecode.code_notes_analyzer import CodeNotesAnalyzer
311
+ orig_analyze = CodeNotesAnalyzer.analyze
312
+
313
+ def _mock_analyze(self, root):
314
+ called.append(root)
315
+ return orig_analyze(self, root)
316
+
317
+ with patch.object(CodeNotesAnalyzer, "analyze", _mock_analyze):
318
+ result = runner.invoke(
319
+ app, ["prepare-context", "fix-bug", str(tmp_path), "--fast"]
320
+ )
321
+ except ImportError:
322
+ pytest.skip("CodeNotesAnalyzer not importable")
323
+
324
+ assert result.exit_code == 0
325
+ # With --fast, code notes analyzer should NOT have been called
326
+ assert not called, "--fast must skip code notes analysis"
327
+
328
+ def test_fast_flag_in_help(self):
329
+ """--fast appears in prepare-context help."""
330
+ result = runner.invoke(app, ["prepare-context", "--help"])
331
+ assert result.exit_code == 0
332
+ assert "--fast" in result.output
333
+
334
+ def test_no_fast_does_not_contaminate_stdout(self, tmp_path):
335
+ """Progress message (when not --fast) must not appear in JSON stdout."""
336
+ (tmp_path / "main.py").write_text("x = 1\n")
337
+ result = runner.invoke(
338
+ app, ["prepare-context", "explain", str(tmp_path)]
339
+ )
340
+ assert result.exit_code == 0
341
+ # stdout must parse as valid JSON
342
+ data = _json(result)
343
+ assert "task" in data
344
+
345
+
346
+ # ── IMPROVEMENT #6: git_context recent_commits ───────────────────────────────
347
+
348
+ class TestRecentCommitsInGitContext:
349
+ def _make_sm_with_commits(self, commits: list) -> SourceMap:
350
+ sm = SourceMap(metadata=AnalysisMetadata(analyzed_path="/tmp/test"))
351
+ sm.git_context = GitContext(
352
+ requested=True,
353
+ branch="main",
354
+ recent_commits=commits,
355
+ change_hotspots=[ChangeHotspot(file="a.py", commit_count=3, last_changed="2026-05-01")],
356
+ uncommitted_changes=UncommittedChanges(staged=[], unstaged=[], untracked=[]),
357
+ )
358
+ return sm
359
+
360
+ def test_recent_commits_present_in_compact_output(self):
361
+ """_compact_git_context includes recent_commits with top-5 entries."""
362
+ commits = [
363
+ CommitRecord(hash="abcd1234", message="feat: add user endpoint", author="alice", date="2026-05-15", files_changed=["a.py"]),
364
+ CommitRecord(hash="efgh5678", message="fix: null pointer in service", author="bob", date="2026-05-14", files_changed=["b.py"]),
365
+ ]
366
+ sm = self._make_sm_with_commits(commits)
367
+ ctx = _compact_git_context(sm)
368
+ assert ctx is not None
369
+ assert "recent_commits" in ctx
370
+ assert len(ctx["recent_commits"]) == 2
371
+
372
+ def test_recent_commits_fields(self):
373
+ """Each recent_commits entry has hash, message, date, author."""
374
+ commits = [
375
+ CommitRecord(
376
+ hash="a0b438a4deadbeef",
377
+ message="Merge pull request #82 into main — very long message that should be truncated at exactly eighty characters total",
378
+ author="m3-dhl",
379
+ date="2026-05-15",
380
+ files_changed=[],
381
+ ),
382
+ ]
383
+ sm = self._make_sm_with_commits(commits)
384
+ ctx = _compact_git_context(sm)
385
+ entry = ctx["recent_commits"][0]
386
+ assert entry["hash"] == "a0b438a4" # 8-char short hash
387
+ assert len(entry["message"]) <= 80
388
+ assert entry["date"] == "2026-05-15"
389
+ assert entry["author"] == "m3-dhl"
390
+
391
+ def test_recent_commits_capped_at_5(self):
392
+ """At most 5 commits in recent_commits."""
393
+ commits = [
394
+ CommitRecord(hash=f"hash{i:08d}", message=f"commit {i}", author="a", date="2026-05-01", files_changed=[])
395
+ for i in range(10)
396
+ ]
397
+ sm = self._make_sm_with_commits(commits)
398
+ ctx = _compact_git_context(sm)
399
+ assert len(ctx["recent_commits"]) == 5
400
+
401
+ def test_no_commits_no_key(self):
402
+ """No recent_commits key when list is empty."""
403
+ sm = self._make_sm_with_commits([])
404
+ ctx = _compact_git_context(sm)
405
+ assert ctx is None or "recent_commits" not in (ctx or {})
406
+
407
+
408
+ # ── IMPROVEMENT #7: --no-redact help text ────────────────────────────────────
409
+
410
+ class TestNoRedactHelp:
411
+ def test_no_redact_help_mentions_env_var_policy(self):
412
+ """--no-redact help text must clarify env var values are never included."""
413
+ result = runner.invoke(app, ["--help"])
414
+ assert result.exit_code == 0
415
+ assert "--no-redact" in result.output
416
+ # The help must not imply env var values will be shown
417
+ # Verify the security policy note is present somewhere in help
418
+ # (exact wording may vary — test for the key concepts)
419
+ assert "security" in result.output.lower() or "policy" in result.output.lower() or "never" in result.output.lower()
File without changes
File without changes