sourcecode 1.30.21__tar.gz → 1.30.23__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 (195) hide show
  1. {sourcecode-1.30.21 → sourcecode-1.30.23}/PKG-INFO +3 -3
  2. {sourcecode-1.30.21 → sourcecode-1.30.23}/README.md +2 -2
  3. {sourcecode-1.30.21 → sourcecode-1.30.23}/pyproject.toml +1 -1
  4. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/cli.py +30 -2
  6. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/prepare_context.py +194 -65
  7. {sourcecode-1.30.21 → sourcecode-1.30.23}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  8. {sourcecode-1.30.21 → sourcecode-1.30.23}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  9. {sourcecode-1.30.21 → sourcecode-1.30.23}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  10. {sourcecode-1.30.21 → sourcecode-1.30.23}/.continue-here.md +0 -0
  11. {sourcecode-1.30.21 → sourcecode-1.30.23}/.github/workflows/build-windows.yml +0 -0
  12. {sourcecode-1.30.21 → sourcecode-1.30.23}/.gitignore +0 -0
  13. {sourcecode-1.30.21 → sourcecode-1.30.23}/.ruff.toml +0 -0
  14. {sourcecode-1.30.21 → sourcecode-1.30.23}/CONTRIBUTING.md +0 -0
  15. {sourcecode-1.30.21 → sourcecode-1.30.23}/LICENSE +0 -0
  16. {sourcecode-1.30.21 → sourcecode-1.30.23}/SECURITY.md +0 -0
  17. {sourcecode-1.30.21 → sourcecode-1.30.23}/docs/privacy.md +0 -0
  18. {sourcecode-1.30.21 → sourcecode-1.30.23}/docs/schema.md +0 -0
  19. {sourcecode-1.30.21 → sourcecode-1.30.23}/raw +0 -0
  20. {sourcecode-1.30.21 → sourcecode-1.30.23}/run_cli.py +0 -0
  21. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/adaptive_scanner.py +0 -0
  22. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/architecture_analyzer.py +0 -0
  23. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/architecture_summary.py +0 -0
  24. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/ast_extractor.py +0 -0
  25. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/classifier.py +0 -0
  26. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/code_notes_analyzer.py +0 -0
  27. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/confidence_analyzer.py +0 -0
  28. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/context_scorer.py +0 -0
  29. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/context_summarizer.py +0 -0
  30. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/contract_model.py +0 -0
  31. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/contract_pipeline.py +0 -0
  32. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/coverage_parser.py +0 -0
  33. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/dependency_analyzer.py +0 -0
  34. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/__init__.py +0 -0
  35. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/base.py +0 -0
  36. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/csproj_parser.py +0 -0
  37. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/dart.py +0 -0
  38. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/dotnet.py +0 -0
  39. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/elixir.py +0 -0
  40. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/go.py +0 -0
  41. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/heuristic.py +0 -0
  42. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/hybrid.py +0 -0
  43. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/java.py +0 -0
  44. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/jvm_ext.py +0 -0
  45. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/nodejs.py +0 -0
  46. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/parsers.py +0 -0
  47. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/php.py +0 -0
  48. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/project.py +0 -0
  49. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/python.py +0 -0
  50. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/ruby.py +0 -0
  51. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/rust.py +0 -0
  52. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/systems.py +0 -0
  53. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/terraform.py +0 -0
  54. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/detectors/tooling.py +0 -0
  55. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/doc_analyzer.py +0 -0
  56. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/entrypoint_classifier.py +0 -0
  57. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/env_analyzer.py +0 -0
  58. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/file_classifier.py +0 -0
  59. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/flow_analyzer.py +0 -0
  60. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/git_analyzer.py +0 -0
  61. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/graph_analyzer.py +0 -0
  62. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/metrics_analyzer.py +0 -0
  63. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/pr_comment_renderer.py +0 -0
  64. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/progress.py +0 -0
  65. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/ranking_engine.py +0 -0
  66. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/redactor.py +0 -0
  67. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/relevance_scorer.py +0 -0
  68. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/repo_classifier.py +0 -0
  69. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/repository_ir.py +0 -0
  70. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/runtime_classifier.py +0 -0
  71. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/scanner.py +0 -0
  72. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/schema.py +0 -0
  73. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/semantic_analyzer.py +0 -0
  74. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/serializer.py +0 -0
  75. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/summarizer.py +0 -0
  76. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/__init__.py +0 -0
  77. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/config.py +0 -0
  78. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/consent.py +0 -0
  79. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/events.py +0 -0
  80. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/filters.py +0 -0
  81. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/telemetry/transport.py +0 -0
  82. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/tree_utils.py +0 -0
  83. {sourcecode-1.30.21 → sourcecode-1.30.23}/src/sourcecode/workspace.py +0 -0
  84. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/__init__.py +0 -0
  85. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/conftest.py +0 -0
  86. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/coverage.xml +0 -0
  87. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  88. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/fastapi_app/src/main.py +0 -0
  89. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  90. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/go_service/go.mod +0 -0
  91. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/jacoco.xml +0 -0
  92. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/latin1_sample.java +0 -0
  93. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/latin1_sample_iso.java +0 -0
  94. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/lcov.info +0 -0
  95. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  96. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/nextjs_app/package.json +0 -0
  97. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  98. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  99. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  100. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  101. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  102. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  103. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  104. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  105. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  106. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  107. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  108. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  109. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  110. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  111. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  112. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  113. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  114. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  115. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  116. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  117. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  118. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  119. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  120. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  121. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  122. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  123. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  124. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  125. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  126. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  127. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  128. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  129. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  130. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_architecture_analyzer.py +0 -0
  131. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_architecture_summary.py +0 -0
  132. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_ast_extractor.py +0 -0
  133. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_block1_reliability.py +0 -0
  134. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_block2_coverage.py +0 -0
  135. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_block5_quality.py +0 -0
  136. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_bug_fixes_v16.py +0 -0
  137. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_classifier.py +0 -0
  138. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_cli.py +0 -0
  139. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_code_notes_analyzer.py +0 -0
  140. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_context_scorer.py +0 -0
  141. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_contract_pipeline.py +0 -0
  142. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_coverage_parser.py +0 -0
  143. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_cross_consistency.py +0 -0
  144. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_dependency_analyzer_node_python.py +0 -0
  145. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_dependency_analyzer_polyglot.py +0 -0
  146. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_dependency_schema.py +0 -0
  147. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_dotnet.py +0 -0
  148. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_go_rust_java.py +0 -0
  149. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_nodejs.py +0 -0
  150. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_php_ruby_dart.py +0 -0
  151. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_python.py +0 -0
  152. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_universal_managed.py +0 -0
  153. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detector_universal_systems.py +0 -0
  154. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_detectors_base.py +0 -0
  155. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_doc_analyzer_jsdom.py +0 -0
  156. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_doc_analyzer_python.py +0 -0
  157. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_encoding_regression.py +0 -0
  158. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_graph_analyzer_polyglot.py +0 -0
  159. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_graph_analyzer_python_node.py +0 -0
  160. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_graph_schema.py +0 -0
  161. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_hybrid_inference.py +0 -0
  162. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration.py +0 -0
  163. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_dependencies.py +0 -0
  164. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_detection.py +0 -0
  165. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_docs.py +0 -0
  166. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_graph_modules.py +0 -0
  167. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_lqn.py +0 -0
  168. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_metrics.py +0 -0
  169. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_multistack.py +0 -0
  170. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_semantics.py +0 -0
  171. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_integration_universal.py +0 -0
  172. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_java_spring_integration.py +0 -0
  173. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_metrics_analyzer.py +0 -0
  174. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_output_ux.py +0 -0
  175. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_packaging.py +0 -0
  176. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_phase1_improvements.py +0 -0
  177. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_pipeline_integrity.py +0 -0
  178. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_real_projects.py +0 -0
  179. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_redactor.py +0 -0
  180. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_repository_ir.py +0 -0
  181. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_scanner.py +0 -0
  182. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_schema.py +0 -0
  183. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_schema_normalization.py +0 -0
  184. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_semantic_analyzer_node.py +0 -0
  185. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_semantic_analyzer_python.py +0 -0
  186. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_semantic_import_resolution.py +0 -0
  187. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_semantic_schema.py +0 -0
  188. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_signal_hierarchy.py +0 -0
  189. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_summarizer.py +0 -0
  190. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_surface_honesty.py +0 -0
  191. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_task_differentiation.py +0 -0
  192. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_telemetry.py +0 -0
  193. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_v131_improvements.py +0 -0
  194. {sourcecode-1.30.21 → sourcecode-1.30.23}/tests/test_v1_10_regressions.py +0 -0
  195. {sourcecode-1.30.21 → sourcecode-1.30.23}/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.21
3
+ Version: 1.30.23
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.21-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.23-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.21
260
+ # sourcecode 1.30.23
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.21-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.30.23-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.21
41
+ # sourcecode 1.30.23
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.21"
7
+ version = "1.30.23"
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.21"
3
+ __version__ = "1.30.23"
@@ -1610,11 +1610,10 @@ def _serialize_relevant_file(f: Any) -> dict:
1610
1610
  d = {k: v for k, v in _asdict(f).items() if v != "" and v is not None}
1611
1611
  reason = d.pop("reason", "") or ""
1612
1612
  why = d.pop("why", "") or ""
1613
+ d.pop("score", None) # score removed from public output (internal ranking only)
1613
1614
  explanation = _make_explanation(reason, why)
1614
1615
  if explanation:
1615
1616
  d["explanation"] = explanation
1616
- if reason:
1617
- d["reason"] = reason
1618
1617
  return d
1619
1618
 
1620
1619
 
@@ -1882,9 +1881,14 @@ def prepare_context_cmd(
1882
1881
  def _task_include(field: str) -> bool:
1883
1882
  return _content_filter.get(field, True)
1884
1883
 
1884
+ _run_id = hashlib.sha256(
1885
+ f"{task}:{target}:{since or ''}:{format or ''}:{symptom or ''}".encode()
1886
+ ).hexdigest()[:16]
1887
+
1885
1888
  out: dict[str, Any] = {
1886
1889
  "task": output.task,
1887
1890
  "goal": output.goal,
1891
+ "run_id": _run_id,
1888
1892
  }
1889
1893
  if _task_include("project_summary"):
1890
1894
  out["project_summary"] = output.project_summary
@@ -1935,6 +1939,30 @@ def prepare_context_cmd(
1935
1939
  _sys.stdout.buffer.write(b"\n")
1936
1940
  _sys.stdout.buffer.flush()
1937
1941
  raise typer.Exit(code=1)
1942
+ if output.ci_decision == "no_changes":
1943
+ # Early exit: no diff — emit minimal JSON without any analysis fields.
1944
+ # Prevents no_changes from ever being serialized alongside relevant_files > 0.
1945
+ _nc_out: dict[str, Any] = {
1946
+ "task": output.task,
1947
+ "ci_decision": "no_changes",
1948
+ "summary": "No changes detected",
1949
+ }
1950
+ if output.since:
1951
+ _nc_out["since"] = output.since
1952
+ if output.analysis_scope:
1953
+ _nc_out["analysis_scope"] = output.analysis_scope
1954
+ _nc_json = json.dumps(_nc_out, indent=2, ensure_ascii=False)
1955
+ if output_path is not None:
1956
+ output_path.write_text(_nc_json, encoding="utf-8")
1957
+ else:
1958
+ import sys as _sys
1959
+ _sys.stdout.buffer.write(_nc_json.encode("utf-8"))
1960
+ _sys.stdout.buffer.write(b"\n")
1961
+ _sys.stdout.buffer.flush()
1962
+ if copy:
1963
+ _copy_to_clipboard(_nc_json)
1964
+ typer.echo("✓ copied to clipboard", err=True)
1965
+ raise typer.Exit()
1938
1966
  if output.ci_decision:
1939
1967
  out["ci_decision"] = output.ci_decision
1940
1968
  if output.since:
@@ -25,6 +25,7 @@ class DiffSourceType(str, Enum):
25
25
  WORKTREE_STAGED = "WORKTREE_STAGED" # git diff --cached
26
26
  GIT_SINCE_REF = "GIT_SINCE_REF" # git diff ref HEAD
27
27
  GIT_RANGE = "GIT_RANGE" # git diff refA refB
28
+ HEAD_MINUS_1 = "HEAD_MINUS_1" # git diff HEAD~1 HEAD (auto-fallback when tree clean)
28
29
 
29
30
 
30
31
  # ─────────────────────────────────────────────────────────────────────────────
@@ -377,6 +378,25 @@ class TaskOutput:
377
378
  analysis_scope: dict = field(default_factory=dict)
378
379
 
379
380
 
381
+ @dataclass
382
+ class CanonicalAnalysisIR:
383
+ """Shared intermediate representation produced by _build_delta_impact.
384
+
385
+ Both delta and review-pr derive their task-specific output from this IR.
386
+ Never recalculate logic per command — render views from this single object.
387
+ """
388
+ relevant_files: list[RelevantFile]
389
+ impact_summary: str
390
+ affected_modules: list[str]
391
+ risk_areas: list[dict]
392
+ why_these_files: dict[str, str]
393
+ analysis_gaps: list[str]
394
+ system_impact: dict
395
+ change_type: list[str]
396
+ dependency_graph_summary: dict
397
+ impact_score_per_file: dict
398
+
399
+
380
400
  # ─────────────────────────────────────────────────────────────────────────────
381
401
  # Builder
382
402
  # ─────────────────────────────────────────────────────────────────────────────
@@ -582,6 +602,83 @@ _FRONTEND_SYMPTOM_MAP: dict[str, list[str]] = {
582
602
  }
583
603
 
584
604
 
605
+ def _build_analysis_scope(
606
+ *,
607
+ task_name: str,
608
+ since: Optional[str],
609
+ delta_baseline: dict,
610
+ pr_scope_source: str,
611
+ pr_uncommitted_files: list,
612
+ ) -> dict:
613
+ """Build analysis_scope metadata from actual git resolution — never hardcoded."""
614
+ if task_name == "review-pr":
615
+ sources = pr_scope_source.split(",") if pr_scope_source else []
616
+ # Derive git_equivalent_command from sources
617
+ cmds: list[str] = []
618
+ if DiffSourceType.GIT_RANGE.value in sources or DiffSourceType.GIT_SINCE_REF.value in sources:
619
+ cmds.append(f"git diff --name-only {since} HEAD")
620
+ if DiffSourceType.HEAD_MINUS_1.value in sources:
621
+ cmds.append("git diff --name-only HEAD~1 HEAD")
622
+ if DiffSourceType.WORKTREE_UNSTAGED.value in sources:
623
+ cmds.append("git diff --name-only")
624
+ if DiffSourceType.WORKTREE_STAGED.value in sources:
625
+ cmds.append("git diff --name-only --cached")
626
+ git_cmd = " + ".join(cmds) if cmds else "git diff --name-only"
627
+ return {
628
+ "sources_used": sources,
629
+ "git_equivalent_command": git_cmd,
630
+ "includes_uncommitted": bool(pr_uncommitted_files),
631
+ "git_resolution": {
632
+ "source_of_truth": "RANGE" if since else (
633
+ "HEAD" if DiffSourceType.HEAD_MINUS_1.value in sources else "WORKTREE"
634
+ ),
635
+ "resolved_diff_strategy": pr_scope_source,
636
+ "commit_count_analyzed": 1 if DiffSourceType.HEAD_MINUS_1.value in sources else (
637
+ 0 if not since else None
638
+ ),
639
+ },
640
+ }
641
+
642
+ # delta
643
+ rpath = delta_baseline.get("resolution_path", "")
644
+ rref = delta_baseline.get("resolved_ref", "HEAD")
645
+
646
+ _COMMITTED_PATHS = {"exact_local_ref", "remote_tracking_ref", "symbolic_ref"}
647
+ if rpath in _COMMITTED_PATHS:
648
+ sources = [DiffSourceType.GIT_SINCE_REF.value]
649
+ git_cmd = f"git diff --name-only {rref} HEAD"
650
+ source_of_truth = "RANGE"
651
+ includes_uncommitted = False
652
+ elif rpath == "head_minus_1_fallback":
653
+ sources = [DiffSourceType.HEAD_MINUS_1.value]
654
+ git_cmd = "git diff --name-only HEAD~1 HEAD"
655
+ source_of_truth = "HEAD"
656
+ includes_uncommitted = False
657
+ elif rpath == "uncommitted_staged":
658
+ sources = [DiffSourceType.WORKTREE_STAGED.value]
659
+ git_cmd = "git diff --name-only --cached"
660
+ source_of_truth = "STAGED"
661
+ includes_uncommitted = True
662
+ else: # uncommitted_unstaged, uncommitted_changes, no_changes_confirmed
663
+ sources = [DiffSourceType.WORKTREE_UNSTAGED.value]
664
+ git_cmd = "git diff --name-only"
665
+ source_of_truth = "WORKTREE"
666
+ includes_uncommitted = True
667
+
668
+ return {
669
+ "sources_used": sources,
670
+ "git_equivalent_command": git_cmd,
671
+ "includes_uncommitted": includes_uncommitted,
672
+ "git_resolution": {
673
+ "source_of_truth": source_of_truth,
674
+ "resolved_diff_strategy": rpath or "unknown",
675
+ "commit_count_analyzed": 1 if rpath == "head_minus_1_fallback" else (
676
+ 0 if rpath == "no_changes_confirmed" else None
677
+ ),
678
+ },
679
+ }
680
+
681
+
585
682
  class TaskContextBuilder:
586
683
  def __init__(self, root: Path) -> None:
587
684
  self.root = root
@@ -851,9 +948,11 @@ class TaskContextBuilder:
851
948
  # For delta task, relevant_files must rank only files changed in the
852
949
  # specified git range, not the full repo by generic entrypoint scoring.
853
950
  _delta_files: Optional[set[str]] = None
951
+ _delta_baseline: dict = {} # resolution metadata threaded into analysis_scope
854
952
  if task_name == "delta":
855
- _delta_raw = self._get_git_changed_files(since=since)
856
- if _delta_raw is None:
953
+ _baseline = self._resolve_git_baseline(since=since)
954
+ _delta_baseline = _baseline
955
+ if _baseline["error"]:
857
956
  # Explicit --since ref couldn't be resolved — hard error, no fallback
858
957
  _avail_branches, _suggested = self._get_available_refs(since or "")
859
958
  _hints: list[str] = []
@@ -880,8 +979,11 @@ class TaskContextBuilder:
880
979
  error_hints=_hints,
881
980
  gaps=[f"Cannot compute delta: git ref '{since}' not found."] + _hints,
882
981
  ci_decision="git_ref_error",
982
+ resolution_path=_baseline.get("resolution_path"),
983
+ diff_validation_status=_baseline.get("diff_validation_status"),
883
984
  )
884
- elif _delta_raw:
985
+ _delta_raw = _baseline["files"]
986
+ if _delta_raw:
885
987
  _delta_files = set(_delta_raw)
886
988
 
887
989
  # ── 5d. review-pr: set _delta_files from pre-resolved git scope ──────────
@@ -934,7 +1036,8 @@ class TaskContextBuilder:
934
1036
  test_set = {p for p in all_paths if self._is_test(p)}
935
1037
  source_set = {p for p in all_paths if not self._is_test(p) and self._is_source(p)}
936
1038
 
937
- # Delta uses a dedicated impact-analysis path never the generic ranker.
1039
+ # Delta and review-pr share CanonicalAnalysisIRcomputed once, rendered per task.
1040
+ _ir: Optional[CanonicalAnalysisIR] = None
938
1041
  _delta_impact_summary: Optional[str] = None
939
1042
  _delta_affected_modules: list[str] = []
940
1043
  _delta_risk_areas: list[dict] = []
@@ -947,23 +1050,22 @@ class TaskContextBuilder:
947
1050
 
948
1051
  if task_name in ("delta", "review-pr"):
949
1052
  _delta_changed_list: list[str] = sorted(_delta_files) if _delta_files else []
950
- (
951
- relevant_files,
952
- _delta_impact_summary,
953
- _delta_affected_modules,
954
- _delta_risk_areas,
955
- _delta_why,
956
- _delta_analysis_gaps,
957
- _delta_system_impact,
958
- _delta_change_type,
959
- _delta_dep_graph_summary,
960
- _delta_impact_score_per_file,
961
- ) = self._build_delta_impact(
1053
+ _ir = self._build_delta_impact(
962
1054
  changed_files=_delta_changed_list,
963
1055
  all_paths=all_paths,
964
1056
  entry_points=entry_points,
965
1057
  since=since,
966
1058
  )
1059
+ relevant_files = _ir.relevant_files
1060
+ _delta_impact_summary = _ir.impact_summary
1061
+ _delta_affected_modules = _ir.affected_modules
1062
+ _delta_risk_areas = _ir.risk_areas
1063
+ _delta_why = _ir.why_these_files
1064
+ _delta_analysis_gaps = _ir.analysis_gaps
1065
+ _delta_system_impact = _ir.system_impact
1066
+ _delta_change_type = _ir.change_type
1067
+ _delta_dep_graph_summary = _ir.dependency_graph_summary
1068
+ _delta_impact_score_per_file = _ir.impact_score_per_file
967
1069
  else:
968
1070
  relevant_files = self._rank_files(
969
1071
  task_name, spec, all_paths, entry_set, test_set,
@@ -1496,7 +1598,11 @@ class TaskContextBuilder:
1496
1598
  changed_files: list[str] = []
1497
1599
  affected_entry_points: list[str] = []
1498
1600
  if task_name in ("delta", "review-pr"):
1499
- changed_files = sorted(_delta_files) if _delta_files else (self._get_git_changed_files(since=since) or [])
1601
+ # For delta: _delta_files already resolved via _resolve_git_baseline no second git call.
1602
+ # For review-pr: _get_git_changed_files fallback still valid as last resort.
1603
+ changed_files = sorted(_delta_files) if _delta_files else (
1604
+ [] if task_name == "delta" else (self._get_git_changed_files(since=since) or [])
1605
+ )
1500
1606
  _ep_set = {ep.path for ep in entry_points}
1501
1607
  # include framework-detected entry points AND files classified as
1502
1608
  # entrypoint/controller/security by artifact taxonomy
@@ -1563,21 +1669,16 @@ class TaskContextBuilder:
1563
1669
  committed_changes=_pr_committed_changes,
1564
1670
  uncommitted_changes=_pr_uncommitted_changes,
1565
1671
  # transparency: explicit diff scope
1566
- analysis_scope={
1567
- "sources_used": _pr_scope_source.split(",") if task_name == "review-pr" and _pr_scope_source else (
1568
- [DiffSourceType.GIT_SINCE_REF.value] if task_name == "delta" and since else
1569
- [DiffSourceType.WORKTREE_UNSTAGED.value] if task_name == "delta" else
1570
- []
1571
- ),
1572
- "git_equivalent_command": (
1573
- f"git diff --name-only {since} HEAD" if since else
1574
- "git diff --name-only"
1575
- ) if task_name in ("delta", "review-pr") else None,
1576
- "includes_uncommitted": bool(
1577
- (task_name == "review-pr" and _pr_uncommitted_files) or
1578
- (task_name == "delta" and not since)
1579
- ),
1580
- } if task_name in ("delta", "review-pr") else {},
1672
+ analysis_scope=_build_analysis_scope(
1673
+ task_name=task_name,
1674
+ since=since,
1675
+ delta_baseline=_delta_baseline,
1676
+ pr_scope_source=_pr_scope_source,
1677
+ pr_uncommitted_files=_pr_uncommitted_files,
1678
+ ) if task_name in ("delta", "review-pr") else {},
1679
+ resolved_since_ref=_delta_baseline.get("resolved_ref") if task_name == "delta" else None,
1680
+ resolution_path=_delta_baseline.get("resolution_path") if task_name == "delta" else None,
1681
+ diff_validation_status=_delta_baseline.get("diff_validation_status") if task_name == "delta" else None,
1581
1682
  )
1582
1683
 
1583
1684
  def render_prompt(self, output: TaskOutput) -> str:
@@ -1943,6 +2044,17 @@ class TaskContextBuilder:
1943
2044
  if DiffSourceType.WORKTREE_STAGED.value not in sources:
1944
2045
  sources.append(DiffSourceType.WORKTREE_STAGED.value)
1945
2046
 
2047
+ # ── HEAD~1 fallback — working tree clean, surface last commit ────────
2048
+ # Without --since: if no unstaged/staged changes exist, fall back to the
2049
+ # last committed diff so a clean tree never silently returns no_changes.
2050
+ if since is None and not committed_files and not uncommitted_files:
2051
+ head_ok = _run("git", "rev-parse", "--verify", "HEAD~1")
2052
+ if head_ok is not None: # HEAD~1 exists
2053
+ head_committed = _run("git", "diff", "--name-only", "--relative", "HEAD~1", "HEAD")
2054
+ if head_committed:
2055
+ committed_files = head_committed
2056
+ sources.append(DiffSourceType.HEAD_MINUS_1.value)
2057
+
1946
2058
  # ── Drop paths outside self.root ──────────────────────────────────────
1947
2059
  def _drop_outside(lst: list[str]) -> list[str]:
1948
2060
  return [f for f in lst if not f.startswith("../") and not f.startswith("..\\")]
@@ -2297,13 +2409,10 @@ class TaskContextBuilder:
2297
2409
  all_paths: list[str],
2298
2410
  entry_points: list,
2299
2411
  since: Optional[str],
2300
- ) -> tuple[list[RelevantFile], str, list[str], list[dict[str, Any]], dict[str, str], list[str]]:
2412
+ ) -> "CanonicalAnalysisIR":
2301
2413
  """Build incremental impact analysis for changed files.
2302
2414
 
2303
- Returns:
2304
- (relevant_files, impact_summary, affected_modules, risk_areas,
2305
- why_these_files, analysis_gaps)
2306
-
2415
+ Returns CanonicalAnalysisIR — the shared IR consumed by delta and review-pr views.
2307
2416
  Changed files are always included in relevant_files (never dropped by score).
2308
2417
  Related files are expanded type-aware: controller→service→repository→mapper chain.
2309
2418
  Scoring is hierarchical by artifact_type, not by heuristic impact_level.
@@ -2471,6 +2580,18 @@ class TaskContextBuilder:
2471
2580
  return "data_access"
2472
2581
  if atype == "service":
2473
2582
  return "service" # annotation-confirmed — not "core_service" (overclaim)
2583
+ # Name-pattern heuristics — only when atype alone gave no verdict.
2584
+ # These are INFERRED (LOW CONFIDENCE) — stem match, not annotation evidence.
2585
+ if any(kw in stem_lower for kw in ("validator", "validation")):
2586
+ return "validation_component"
2587
+ if any(kw in stem_lower for kw in ("controller", "resource", "endpoint", "rest")):
2588
+ return "external_interface"
2589
+ if any(kw in stem_lower for kw in ("service", "svc", "usecase", "facade")):
2590
+ return "service"
2591
+ if any(kw in stem_lower for kw in ("repository", "repo", "dao", "store")):
2592
+ return "data_access"
2593
+ if any(kw in stem_lower for kw in ("config", "configuration", "settings", "properties")):
2594
+ return "configuration"
2474
2595
  return "unclassified" # no role claim without evidence
2475
2596
 
2476
2597
  def _structured_why(path: str, atype: str, module: str, role: str, risk_areas: list[str], cls_confidence: str = "low") -> str:
@@ -2491,17 +2612,17 @@ class TaskContextBuilder:
2491
2612
  return " | ".join(parts)
2492
2613
 
2493
2614
  if not changed_files:
2494
- return (
2495
- [],
2496
- "No changes detected — verify the git ref passed to --since",
2497
- [],
2498
- [],
2499
- {},
2500
- ["No changed files found. Check that --since ref exists and the diff is non-empty."],
2501
- {}, # system_impact
2502
- [], # change_type
2503
- {"edges": [], "propagation_depth": 0}, # dependency_graph_summary
2504
- {}, # impact_score_per_file
2615
+ return CanonicalAnalysisIR(
2616
+ relevant_files=[],
2617
+ impact_summary="No changes detected — verify the git ref passed to --since",
2618
+ affected_modules=[],
2619
+ risk_areas=[],
2620
+ why_these_files={},
2621
+ analysis_gaps=["No changed files found. Check that --since ref exists and the diff is non-empty."],
2622
+ system_impact={},
2623
+ change_type=[],
2624
+ dependency_graph_summary={"edges": [], "propagation_depth": 0},
2625
+ impact_score_per_file={},
2505
2626
  )
2506
2627
 
2507
2628
  ep_paths = {ep.path for ep in entry_points}
@@ -3026,17 +3147,17 @@ class TaskContextBuilder:
3026
3147
  " — related file expansion uses directory proximity only"
3027
3148
  )
3028
3149
 
3029
- return (
3030
- relevant,
3031
- impact_summary,
3032
- sorted(affected_modules_set),
3033
- risk_areas_out,
3034
- why,
3035
- analysis_gaps,
3036
- system_impact,
3037
- aggregate_change_type,
3038
- dependency_graph_summary,
3039
- impact_score_per_file,
3150
+ return CanonicalAnalysisIR(
3151
+ relevant_files=relevant,
3152
+ impact_summary=impact_summary,
3153
+ affected_modules=sorted(affected_modules_set),
3154
+ risk_areas=risk_areas_out,
3155
+ why_these_files=why,
3156
+ analysis_gaps=analysis_gaps,
3157
+ system_impact=system_impact,
3158
+ change_type=aggregate_change_type,
3159
+ dependency_graph_summary=dependency_graph_summary,
3160
+ impact_score_per_file=impact_score_per_file,
3040
3161
  )
3041
3162
 
3042
3163
  def _resolve_git_baseline(self, since: Optional[str]) -> dict[str, Any]:
@@ -3136,25 +3257,33 @@ class TaskContextBuilder:
3136
3257
  }
3137
3258
 
3138
3259
  else:
3139
- # No since: uncommitted changes first
3260
+ # No since: unstaged staged → HEAD~1 — never error, clean tree is valid
3140
3261
  ok, out = _run("diff", "--name-only", "--relative", timeout=10)
3141
3262
  if ok:
3142
3263
  files = [line.strip() for line in out.splitlines() if line.strip()]
3143
3264
  if files:
3144
- return _make(files, "HEAD", "uncommitted_changes")
3265
+ return _make(files, "HEAD", "uncommitted_unstaged")
3145
3266
 
3146
- # HEAD~1 fallback
3267
+ # Staged (committed to index but not yet to history)
3268
+ ok, out = _run("diff", "--name-only", "--cached", "--relative", timeout=10)
3269
+ if ok:
3270
+ files = [line.strip() for line in out.splitlines() if line.strip()]
3271
+ if files:
3272
+ return _make(files, "HEAD", "uncommitted_staged")
3273
+
3274
+ # HEAD~1 fallback — working tree clean, surface last commit
3147
3275
  if _verify("HEAD~1"):
3148
3276
  files = _diff("HEAD~1")
3149
3277
  if files is not None:
3150
3278
  return _make(files or [], "HEAD~1", "head_minus_1_fallback")
3151
3279
 
3280
+ # Confirmed no changes: first commit or empty repo
3152
3281
  return {
3153
3282
  "files": [],
3154
3283
  "resolved_ref": "HEAD",
3155
- "resolution_path": "unresolvable",
3156
- "diff_validation_status": "invalid_ref",
3157
- "error": True,
3284
+ "resolution_path": "no_changes_confirmed",
3285
+ "diff_validation_status": "valid_empty",
3286
+ "error": False,
3158
3287
  }
3159
3288
 
3160
3289
  def _get_uncommitted_changed_files(self) -> list[str]:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes