sourcecode 1.6.0__tar.gz → 1.8.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 (180) hide show
  1. {sourcecode-1.6.0 → sourcecode-1.8.0}/PKG-INFO +1 -1
  2. {sourcecode-1.6.0 → sourcecode-1.8.0}/pyproject.toml +1 -1
  3. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/__init__.py +1 -1
  4. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/adaptive_scanner.py +2 -1
  5. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/cli.py +104 -112
  6. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/contract_pipeline.py +7 -1
  7. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/prepare_context.py +74 -5
  8. sourcecode-1.8.0/src/sourcecode/progress.py +110 -0
  9. sourcecode-1.8.0/tests/test_bug_fixes_v16.py +250 -0
  10. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_cli.py +11 -6
  11. {sourcecode-1.6.0 → sourcecode-1.8.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  12. {sourcecode-1.6.0 → sourcecode-1.8.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  13. {sourcecode-1.6.0 → sourcecode-1.8.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  14. {sourcecode-1.6.0 → sourcecode-1.8.0}/.continue-here.md +0 -0
  15. {sourcecode-1.6.0 → sourcecode-1.8.0}/.github/workflows/build-windows.yml +0 -0
  16. {sourcecode-1.6.0 → sourcecode-1.8.0}/.gitignore +0 -0
  17. {sourcecode-1.6.0 → sourcecode-1.8.0}/.ruff.toml +0 -0
  18. {sourcecode-1.6.0 → sourcecode-1.8.0}/CONTRIBUTING.md +0 -0
  19. {sourcecode-1.6.0 → sourcecode-1.8.0}/LICENSE +0 -0
  20. {sourcecode-1.6.0 → sourcecode-1.8.0}/README.md +0 -0
  21. {sourcecode-1.6.0 → sourcecode-1.8.0}/SECURITY.md +0 -0
  22. {sourcecode-1.6.0 → sourcecode-1.8.0}/docs/privacy.md +0 -0
  23. {sourcecode-1.6.0 → sourcecode-1.8.0}/docs/schema.md +0 -0
  24. {sourcecode-1.6.0 → sourcecode-1.8.0}/raw +0 -0
  25. {sourcecode-1.6.0 → sourcecode-1.8.0}/run_cli.py +0 -0
  26. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/architecture_analyzer.py +0 -0
  27. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/architecture_summary.py +0 -0
  28. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/ast_extractor.py +0 -0
  29. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/classifier.py +0 -0
  30. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  31. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/confidence_analyzer.py +0 -0
  32. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/context_scorer.py +0 -0
  33. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/context_summarizer.py +0 -0
  34. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/contract_model.py +0 -0
  35. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/coverage_parser.py +0 -0
  36. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/dependency_analyzer.py +0 -0
  37. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/__init__.py +0 -0
  38. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/base.py +0 -0
  39. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  40. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/dart.py +0 -0
  41. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/dotnet.py +0 -0
  42. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/elixir.py +0 -0
  43. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/go.py +0 -0
  44. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/heuristic.py +0 -0
  45. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/hybrid.py +0 -0
  46. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/java.py +0 -0
  47. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  48. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/nodejs.py +0 -0
  49. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/parsers.py +0 -0
  50. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/php.py +0 -0
  51. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/project.py +0 -0
  52. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/python.py +0 -0
  53. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/ruby.py +0 -0
  54. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/rust.py +0 -0
  55. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/systems.py +0 -0
  56. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/terraform.py +0 -0
  57. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/detectors/tooling.py +0 -0
  58. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/doc_analyzer.py +0 -0
  59. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  60. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/env_analyzer.py +0 -0
  61. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/file_classifier.py +0 -0
  62. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/git_analyzer.py +0 -0
  63. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/graph_analyzer.py +0 -0
  64. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/metrics_analyzer.py +0 -0
  65. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/ranking_engine.py +0 -0
  66. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/redactor.py +0 -0
  67. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/relevance_scorer.py +0 -0
  68. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/repo_classifier.py +0 -0
  69. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/runtime_classifier.py +0 -0
  70. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/scanner.py +0 -0
  71. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/schema.py +0 -0
  72. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/semantic_analyzer.py +0 -0
  73. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/serializer.py +0 -0
  74. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/summarizer.py +0 -0
  75. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/__init__.py +0 -0
  76. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/config.py +0 -0
  77. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/consent.py +0 -0
  78. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/events.py +0 -0
  79. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/filters.py +0 -0
  80. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/telemetry/transport.py +0 -0
  81. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/tree_utils.py +0 -0
  82. {sourcecode-1.6.0 → sourcecode-1.8.0}/src/sourcecode/workspace.py +0 -0
  83. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/__init__.py +0 -0
  84. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/conftest.py +0 -0
  85. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/coverage.xml +0 -0
  86. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  87. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  88. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  89. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/go_service/go.mod +0 -0
  90. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/jacoco.xml +0 -0
  91. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/lcov.info +0 -0
  92. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  93. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/nextjs_app/package.json +0 -0
  94. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  95. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  96. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  97. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  98. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  99. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  100. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  101. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  102. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  103. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  104. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  105. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  106. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  107. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  108. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  109. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  110. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  111. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  112. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  113. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  114. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  115. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  116. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  117. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  118. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  119. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  120. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  121. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  122. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  123. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  124. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_architecture_analyzer.py +0 -0
  125. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_architecture_summary.py +0 -0
  126. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_ast_extractor.py +0 -0
  127. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_block1_reliability.py +0 -0
  128. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_block2_coverage.py +0 -0
  129. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_block5_quality.py +0 -0
  130. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_classifier.py +0 -0
  131. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_code_notes_analyzer.py +0 -0
  132. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_context_scorer.py +0 -0
  133. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_contract_pipeline.py +0 -0
  134. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_coverage_parser.py +0 -0
  135. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_cross_consistency.py +0 -0
  136. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  137. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  138. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_dependency_schema.py +0 -0
  139. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_dotnet.py +0 -0
  140. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_go_rust_java.py +0 -0
  141. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_nodejs.py +0 -0
  142. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_php_ruby_dart.py +0 -0
  143. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_python.py +0 -0
  144. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_universal_managed.py +0 -0
  145. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detector_universal_systems.py +0 -0
  146. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_detectors_base.py +0 -0
  147. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  148. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_doc_analyzer_python.py +0 -0
  149. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  150. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_graph_analyzer_python_node.py +0 -0
  151. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_graph_schema.py +0 -0
  152. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_hybrid_inference.py +0 -0
  153. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration.py +0 -0
  154. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_dependencies.py +0 -0
  155. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_detection.py +0 -0
  156. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_docs.py +0 -0
  157. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_graph_modules.py +0 -0
  158. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_lqn.py +0 -0
  159. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_metrics.py +0 -0
  160. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_multistack.py +0 -0
  161. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_semantics.py +0 -0
  162. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_integration_universal.py +0 -0
  163. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_java_spring_integration.py +0 -0
  164. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_metrics_analyzer.py +0 -0
  165. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_packaging.py +0 -0
  166. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_phase1_improvements.py +0 -0
  167. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_pipeline_integrity.py +0 -0
  168. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_real_projects.py +0 -0
  169. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_redactor.py +0 -0
  170. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_scanner.py +0 -0
  171. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_schema.py +0 -0
  172. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_schema_normalization.py +0 -0
  173. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_semantic_analyzer_node.py +0 -0
  174. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_semantic_analyzer_python.py +0 -0
  175. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_semantic_import_resolution.py +0 -0
  176. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_semantic_schema.py +0 -0
  177. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_signal_hierarchy.py +0 -0
  178. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_summarizer.py +0 -0
  179. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_telemetry.py +0 -0
  180. {sourcecode-1.6.0 → sourcecode-1.8.0}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.6.0"
7
+ version = "1.8.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.6.0"
3
+ __version__ = "1.8.0"
@@ -135,7 +135,8 @@ class AdaptiveScanner:
135
135
  if current_depth >= n:
136
136
  # At or inside the source root
137
137
  if rel_parts[:n] == src_parts:
138
- return src_max # definite source territoryearly exit
138
+ # base_depth acts as a floor explicit --depth always wins
139
+ return max(src_max, self.base_depth)
139
140
  else:
140
141
  # Ancestor check: src_parts starts with rel_parts?
141
142
  if src_parts[:current_depth] == rel_parts:
@@ -10,6 +10,7 @@ import typer
10
10
 
11
11
  from sourcecode import __version__
12
12
  from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
13
+ from sourcecode.progress import Progress
13
14
 
14
15
 
15
16
  # ---------------------------------------------------------------------------
@@ -138,18 +139,19 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
138
139
  return issues
139
140
 
140
141
  _HELP = """\
141
- Deterministic codebase context for AI coding agents.
142
+ Compressed AI-ready context for Java/Spring enterprise codebases.
142
143
 
143
- [bold]Usage:[/bold]
144
- sourcecode [dim]# analyze current directory[/dim]
145
- sourcecode /path/to/repo [dim]# analyze specific path[/dim]
146
- sourcecode --agent [dim]# structured output for AI agents[/dim]
144
+ [bold]Examples:[/bold]
145
+ sourcecode saint-server --compact
146
+ sourcecode . --changed-only --git-context
147
+ sourcecode saint-server --symbol SeguridadRestController
148
+ sourcecode prepare-context onboard saint-server
149
+ sourcecode prepare-context delta . --since main
147
150
 
148
151
  [bold]Subcommands:[/bold]
149
- prepare-context TASK [PATH] [dim]# task-specific context[/dim]
152
+ prepare-context TASK [PATH] [dim]# task-specific context (onboard, delta, fix-bug, ...)[/dim]
150
153
  telemetry status|enable|disable
151
154
  version
152
- config
153
155
  """
154
156
 
155
157
  # Known subcommand names — tokens matching these are routed as subcommands,
@@ -352,58 +354,55 @@ def main(
352
354
  "json",
353
355
  "--format",
354
356
  "-f",
355
- help="Output format: json (default) or yaml. Both carry identical data — yaml is more human-readable, json is preferred for agent pipelines.",
357
+ help="Output format: json (default) or yaml.",
356
358
  show_default=True,
357
359
  ),
358
360
  output: Optional[Path] = typer.Option(
359
361
  None,
360
362
  "--output",
361
363
  "-o",
362
- help="Write output to a file instead of stdout. Useful for storing analysis snapshots or piping to downstream tools.",
364
+ help="Write output to a file instead of stdout.",
363
365
  ),
364
366
  compact: bool = typer.Option(
365
367
  False,
366
368
  "--compact",
367
369
  help=(
368
- "Compact output (~600–800 tokens): project type, stacks, production entry points, "
369
- "dependency summary, confidence summary, and analysis gaps. "
370
- "Omits file tree, raw dependency lists, docs, and module graph. "
371
- "Designed for agent context windows. Automatically enables --dependencies, --env-map, and --code-notes."
370
+ "High-signal summary (~600–800 tokens): stacks, entry points, "
371
+ "dependency summary, confidence, and gaps. "
372
+ "Optimized for agent context windows."
372
373
  ),
373
374
  ),
374
375
  dependencies: bool = typer.Option(
375
376
  False,
376
377
  "--dependencies",
377
- help=(
378
- "Analyze direct and transitive dependencies. Reads manifests (pyproject.toml, package.json, go.mod, etc.) "
379
- "and lockfiles when available. Adds dependency_summary and key_dependencies to output."
380
- ),
378
+ hidden=True,
379
+ help="Analyze direct dependencies from manifests and lockfiles.",
381
380
  ),
382
381
  graph_modules: bool = typer.Option(
383
382
  False,
384
383
  "--graph-modules",
385
- help=(
386
- "Include a structural module graph: nodes (files/symbols) and edges (imports, calls, contains). "
387
- "Useful for understanding coupling and call flows. Adds module_graph to output. "
388
- "Combine with --graph-detail and --graph-edges to control scope."
389
- ),
384
+ hidden=True,
385
+ help="Include a structural module graph in output.",
390
386
  ),
391
387
  graph_detail: str = typer.Option(
392
388
  "high",
393
389
  "--graph-detail",
394
- help="Detail level for --graph-modules: high (top modules by importance), medium (filtered by relevance), full (all nodes and edges). Default: high.",
390
+ hidden=True,
391
+ help="Detail level for --graph-modules: high, medium, full.",
395
392
  show_default=True,
396
393
  ),
397
394
  max_nodes: Optional[int] = typer.Option(
398
395
  None,
399
396
  "--max-nodes",
400
- help="Maximum number of nodes in --graph-modules output when using high or medium detail. Prevents oversized graphs in large codebases.",
397
+ hidden=True,
398
+ help="Maximum nodes in --graph-modules output.",
401
399
  min=1,
402
400
  ),
403
401
  graph_edges: Optional[str] = typer.Option(
404
402
  None,
405
403
  "--graph-edges",
406
- help="Edge types for --graph-modules, comma-separated: imports,calls,contains,extends. Default: all available. Example: --graph-edges imports,calls",
404
+ hidden=True,
405
+ help="Edge types for --graph-modules, comma-separated: imports,calls,contains,extends.",
407
406
  ),
408
407
  no_tree: bool = typer.Option(
409
408
  False,
@@ -414,18 +413,13 @@ def main(
414
413
  tree: bool = typer.Option(
415
414
  False,
416
415
  "--tree",
417
- help=(
418
- "Include the full file_tree and flat file_paths list in output (deep-dive layer). "
419
- "Adds significant size — use when the agent needs to browse the full file structure."
420
- ),
416
+ hidden=True,
417
+ help="Include the full file_tree and flat file_paths list in output.",
421
418
  ),
422
419
  no_redact: bool = typer.Option(
423
420
  False,
424
421
  "--no-redact",
425
- help=(
426
- "Disable automatic secret redaction. By default, potential secrets (API keys, tokens, passwords) "
427
- "are replaced with [REDACTED]. Use with caution — output may contain sensitive values."
428
- ),
422
+ help="Disable secret redaction. Use with caution — output may contain sensitive values.",
429
423
  ),
430
424
  version: Optional[bool] = typer.Option(
431
425
  None,
@@ -438,122 +432,97 @@ def main(
438
432
  depth: int = typer.Option(
439
433
  4,
440
434
  "--depth",
441
- help=(
442
- "Maximum depth for file tree traversal (default: 4, range: 1–20). "
443
- "Increase for deeply nested projects — Maven/Java requires at least 8 (src/main/java/...)."
444
- ),
435
+ help="File tree traversal depth (default: 4). Java/Maven projects auto-adjust to 12.",
445
436
  min=1,
446
437
  max=20,
447
438
  ),
448
439
  docs: bool = typer.Option(
449
440
  False,
450
441
  "--docs",
451
- help="Extract documentation: docstrings, function signatures, and module-level comments. Adds doc_summary and docs to output. Combine with --docs-depth to control coverage.",
442
+ hidden=True,
443
+ help="Extract documentation: docstrings, function signatures, and module-level comments.",
452
444
  ),
453
445
  docs_depth: str = typer.Option(
454
446
  "symbols",
455
447
  "--docs-depth",
456
- help="Documentation extraction depth: module (module-level only), symbols (functions and classes), full (all symbols including private). Default: symbols.",
448
+ hidden=True,
449
+ help="Documentation extraction depth: module, symbols, full.",
457
450
  show_default=True,
458
451
  ),
459
452
  full_metrics: bool = typer.Option(
460
453
  False,
461
454
  "--full-metrics",
462
- help=(
463
- "Technical audit: lines of code, symbol counts, cyclomatic complexity, and test coverage per file. "
464
- "Produces file_metrics and metrics_summary. "
465
- "Not included in --agent output — designed for CI pipelines and code review tools, not as primary agent context."
466
- ),
455
+ hidden=True,
456
+ help="Technical audit: LOC, complexity, test coverage per file.",
467
457
  ),
468
458
  semantics: bool = typer.Option(
469
459
  False,
470
460
  "--semantics",
471
- help=(
472
- "Semantic analysis: cross-file symbol resolution, call graph with confidence levels, and import linking. "
473
- "Adds semantic_calls, semantic_symbols, semantic_links, semantic_summary, and hotspots (files ranked by fan-in/fan-out). "
474
- "Slower than default analysis — skip for quick scans. "
475
- "Confidence degrades on dynamic dispatch, decorators, and generated code."
476
- ),
461
+ hidden=True,
462
+ help="Cross-file symbol resolution and call graph analysis.",
477
463
  ),
478
464
  architecture: bool = typer.Option(
479
465
  False,
480
466
  "--architecture",
481
- help=(
482
- "Architectural inference: detect functional layers (MVC/layered/hexagonal), bounded contexts, "
483
- "and dominant structural patterns. Adds architecture to output. "
484
- "Confidence is low when based on directory names alone — combine with --semantics for higher accuracy."
485
- ),
467
+ hidden=True,
468
+ help="Architectural layer inference (MVC/hexagonal/layered).",
486
469
  ),
487
470
  git_context: bool = typer.Option(
488
471
  False,
489
472
  "--git-context",
490
473
  "-g",
491
- help="Include git activity: recent commits, change hotspots (most frequently modified files), pending uncommitted changes, and contributors. Adds git_context to output.",
474
+ help="Include git activity: recent commits, change hotspots, and uncommitted changes.",
492
475
  ),
493
476
  git_depth: int = typer.Option(
494
477
  20,
495
478
  "--git-depth",
496
- help="Number of recent commits to include with --git-context (default: 20, max: 100).",
479
+ hidden=True,
480
+ help="Number of recent commits to include with --git-context (default: 20).",
497
481
  min=1,
498
482
  max=100,
499
483
  ),
500
484
  git_days: int = typer.Option(
501
485
  90,
502
486
  "--git-days",
503
- help="Time window in days for detecting change hotspots with --git-context (default: 90). Hotspots are files with the most commits in this window.",
487
+ hidden=True,
488
+ help="Time window in days for change hotspot detection (default: 90).",
504
489
  min=1,
505
490
  max=3650,
506
491
  ),
507
492
  env_map: bool = typer.Option(
508
493
  False,
509
494
  "--env-map",
510
- help="Map environment variables: keys, types (string/int/bool/url/path), categories (database/auth/service/...), and which files reference them. Adds env_map and env_summary.",
495
+ hidden=True,
496
+ help="Map environment variables referenced across the codebase.",
511
497
  ),
512
498
  code_notes: bool = typer.Option(
513
499
  False,
514
500
  "--code-notes",
515
- help=(
516
- "Extract inline annotations: TODO, FIXME, HACK, NOTE, DEPRECATED, WARNING, BUG, XXX, OPTIMIZE — "
517
- "with file location and enclosing symbol. "
518
- "Also detects Architecture Decision Records (ADRs) in docs/decisions/, docs/adr/, and similar paths."
519
- ),
501
+ hidden=True,
502
+ help="Extract inline annotations: TODO, FIXME, HACK, DEPRECATED, ADRs.",
520
503
  ),
521
504
  agent: bool = typer.Option(
522
505
  False,
523
506
  "--agent",
524
- help=(
525
- "Agent-optimized output: structured, noise-free JSON for AI consumption. "
526
- "Automatically enables --dependencies, --env-map, and --code-notes. Suppresses file tree. "
527
- "Output includes: identity, entry points, architecture, runtime dependencies, "
528
- "operational signals, confidence summary, and analysis gaps. No empty sections."
529
- ),
507
+ help="Structured noise-free JSON for AI agents: identity, entry points, dependencies, confidence, gaps.",
530
508
  ),
531
509
  trace_pipeline: bool = typer.Option(
532
510
  False,
533
511
  "--trace-pipeline",
534
- help=(
535
- "Diagnostic mode: include pipeline_trace in output showing every candidate, filter decision, "
536
- "and data origin across all pipeline stages. "
537
- "Use to diagnose unexpected or contaminated results. Not intended for normal agent use."
538
- ),
512
+ hidden=True,
513
+ help="Diagnostic: include full pipeline trace in output.",
539
514
  ),
540
515
  mode: str = typer.Option(
541
516
  "contract",
542
517
  "--mode",
543
- help=(
544
- "Output mode: contract (default) | standard | raw. "
545
- "contract: minimal per-file contracts — exports, signatures, deps. "
546
- "Smallest output, recommended for AI agents. "
547
- "minimal is accepted as an alias for contract. "
548
- "standard: full per-file detail with imports, relevance scores, extraction method. "
549
- "raw: project-level analysis only (stacks, entry points, dependency summary). "
550
- "No per-file contracts."
551
- ),
518
+ hidden=True,
519
+ help="Output mode: contract (default) | standard | raw.",
552
520
  ),
553
521
  max_symbols: Optional[int] = typer.Option(
554
522
  None,
555
523
  "--max-symbols",
556
- help="Limit total exported semantic nodes across all file contracts. Trims lowest-ranked files first.",
524
+ hidden=True,
525
+ help="Limit total exported semantic nodes across all file contracts.",
557
526
  min=1,
558
527
  ),
559
528
  dependency_depth: int = typer.Option(
@@ -567,22 +536,25 @@ def main(
567
536
  entrypoints_only: bool = typer.Option(
568
537
  False,
569
538
  "--entrypoints-only",
570
- help="Contract mode: include only files that are runtime entrypoints or have exported symbols (public API surface). Note: 'entrypoints' here includes all files with exports, not strictly detected runtime entry points.",
539
+ hidden=True,
540
+ help="Include only files with exported symbols or runtime entrypoints.",
571
541
  ),
572
542
  changed_only: bool = typer.Option(
573
543
  False,
574
544
  "--changed-only",
575
- help="Contract mode: include only git-modified files (staged, unstaged, untracked).",
545
+ help="Limit output to git-modified files (staged, unstaged, untracked).",
576
546
  ),
577
547
  rank_by: str = typer.Option(
578
548
  "relevance",
579
549
  "--rank-by",
550
+ hidden=True,
580
551
  help="Contract ranking strategy: relevance (default) | centrality | git-churn.",
581
552
  ),
582
553
  emit_graph: bool = typer.Option(
583
554
  False,
584
555
  "--emit-graph",
585
- help="Contract mode: include a compact dependency graph (nodes + edges) in output.",
556
+ hidden=True,
557
+ help="Include a compact dependency graph in contract output.",
586
558
  ),
587
559
  compress_types: bool = typer.Option(
588
560
  False,
@@ -593,16 +565,13 @@ def main(
593
565
  symbol: Optional[str] = typer.Option(
594
566
  None,
595
567
  "--symbol",
596
- help="Contract mode: extract localized context for a specific symbol name. Returns defining file + all importers.",
568
+ help="Extract context for a specific symbol: defining file + all importers.",
597
569
  ),
598
570
  max_importers: int = typer.Option(
599
571
  50,
600
572
  "--max-importers",
601
- help=(
602
- "Maximum importer files returned by --symbol (default: 50). "
603
- "Popular symbols can have hundreds of importers — this prevents output explosion. "
604
- "Defining files are never truncated. Override: --symbol Foo --max-importers 200."
605
- ),
573
+ hidden=True,
574
+ help="Maximum importer files returned by --symbol (default: 50).",
606
575
  min=1,
607
576
  max=10000,
608
577
  ),
@@ -788,7 +757,7 @@ def main(
788
757
  # Require at least 8: src(1)+main(2)+java(3)+com(4)+co(5)+app(6)+module(7)+file.
789
758
  _java_manifest_names = {"pom.xml", "build.gradle", "build.gradle.kts"}
790
759
  _is_java = any(Path(m).name in _java_manifest_names for m in manifests)
791
- _java_min_depth = 10
760
+ _java_min_depth = 12
792
761
  effective_depth = max(depth, _java_min_depth) if _is_java and depth < _java_min_depth else depth
793
762
 
794
763
  # --agent: enable signal analyzers; output via agent_view (not compact)
@@ -797,22 +766,9 @@ def main(
797
766
  env_map = True
798
767
  code_notes = True
799
768
  no_tree = True # agents never need the raw file tree
800
- typer.echo("[agent] dependencies env-map code-notes (no-tree)", err=True)
801
- # Warn about flags that are computed but excluded from agent_view output
802
- _agent_suppressed: list[str] = []
803
- if full_metrics:
804
- _agent_suppressed.append("--full-metrics")
805
- if graph_modules:
806
- _agent_suppressed.append("--graph-modules")
807
- if docs:
808
- _agent_suppressed.append("--docs")
809
- if _agent_suppressed:
810
- typer.echo(
811
- f"[agent] warning: {', '.join(_agent_suppressed)} computed but excluded "
812
- "from --agent output — agent_view does not include these sections. "
813
- "Remove these flags to skip unnecessary computation.",
814
- err=True,
815
- )
769
+
770
+ _progress = Progress()
771
+ _progress.start("scanning files")
816
772
 
817
773
  scanner = AdaptiveScanner(target, topology=_topology, base_depth=effective_depth)
818
774
  raw_tree = scanner.scan_tree()
@@ -850,6 +806,7 @@ def main(
850
806
  node = child
851
807
  return pruned
852
808
 
809
+ _progress.update("parsing manifests")
853
810
  file_tree = filter_sensitive_files(raw_tree)
854
811
  detector = ProjectDetector(build_default_detectors())
855
812
  workspace_analysis = WorkspaceAnalyzer().analyze(target, manifests)
@@ -1318,6 +1275,7 @@ def main(
1318
1275
 
1319
1276
  # Git Context (--git-context flag)
1320
1277
  if git_context:
1278
+ _progress.update("git analysis")
1321
1279
  from sourcecode.git_analyzer import GitAnalyzer
1322
1280
  sm.git_context = GitAnalyzer().analyze(target, depth=git_depth, days=git_days)
1323
1281
 
@@ -1399,7 +1357,33 @@ def main(
1399
1357
  ))
1400
1358
  sm = _replace(sm, pipeline_trace=_trace.build_trace())
1401
1359
 
1360
+ # Pre-compute uncommitted files for --changed-only.
1361
+ # The contract pipeline filter and git_context are two separate subsystems;
1362
+ # wire them here so the pipeline uses git_context data, not an independent git call.
1363
+ _allowed_changed_files: Optional[set[str]] = None
1364
+ if changed_only:
1365
+ from sourcecode.git_analyzer import GitAnalyzer as _GitAnalyzerEarly
1366
+ try:
1367
+ _gc_early = _GitAnalyzerEarly().analyze(target, depth=1, days=1)
1368
+ _bad_gc = {"no_git_repo", "git_not_found", "git_timeout"}
1369
+ if _gc_early and not (_bad_gc & set(_gc_early.limitations)):
1370
+ _uc = _gc_early.uncommitted_changes
1371
+ if _uc:
1372
+ _allowed_changed_files = (
1373
+ set(_uc.staged) | set(_uc.unstaged) | set(_uc.untracked)
1374
+ )
1375
+ if not _allowed_changed_files:
1376
+ typer.echo(
1377
+ "[changed-only] git unavailable or no uncommitted changes — falling back to full scan.",
1378
+ err=True,
1379
+ )
1380
+ changed_only = False
1381
+ except Exception:
1382
+ typer.echo("[changed-only] git error — falling back to full scan.", err=True)
1383
+ changed_only = False
1384
+
1402
1385
  # Contract pipeline — runs for mode=contract|standard|deep|hybrid (skip for raw)
1386
+ _progress.update("extracting contracts")
1403
1387
  _is_contract_mode = mode in ("contract", "standard")
1404
1388
  _pipeline_error = False
1405
1389
  if _is_contract_mode:
@@ -1432,6 +1416,7 @@ def main(
1432
1416
  max_importers=max_importers,
1433
1417
  semantic_calls=sm.semantic_calls or None,
1434
1418
  code_notes=sm.code_notes or None,
1419
+ allowed_changed_files=_allowed_changed_files,
1435
1420
  **_java_pipeline_kwargs,
1436
1421
  )
1437
1422
  except Exception as _exc:
@@ -1469,6 +1454,7 @@ def main(
1469
1454
  )
1470
1455
 
1471
1456
  # 4. Serialize
1457
+ _progress.update("serializing output")
1472
1458
  if _is_contract_mode and not agent:
1473
1459
  from sourcecode.serializer import contract_view as _contract_view
1474
1460
  _depth = _CONTRACT_DEPTH.get(mode, "minimal")
@@ -1560,6 +1546,7 @@ def main(
1560
1546
  pass
1561
1547
 
1562
1548
  # 6. Write output (CLI-04)
1549
+ _progress.finish()
1563
1550
  write_output(content, output=output)
1564
1551
 
1565
1552
  if _pipeline_error:
@@ -1706,8 +1693,13 @@ def prepare_context_cmd(
1706
1693
  if llm_prompt:
1707
1694
  out["llm_prompt"] = builder.render_prompt(output)
1708
1695
 
1696
+ import sys as _sys
1709
1697
  _pc_content = json.dumps(out, indent=2, ensure_ascii=False)
1710
- typer.echo(_pc_content)
1698
+ _pc_bytes = _pc_content.encode("utf-8")
1699
+ _sys.stdout.buffer.write(_pc_bytes)
1700
+ if not _pc_content.endswith("\n"):
1701
+ _sys.stdout.buffer.write(b"\n")
1702
+ _sys.stdout.buffer.flush()
1711
1703
 
1712
1704
  if copy:
1713
1705
  _trimmed = _pc_content.strip()
@@ -183,6 +183,7 @@ class ContractPipeline:
183
183
  code_notes: Optional[list] = None,
184
184
  max_contracts: Optional[int] = _MAX_CONTRACTS,
185
185
  min_score: Optional[float] = None,
186
+ allowed_changed_files: Optional[set[str]] = None,
186
187
  ) -> tuple[list[FileContract], ContractSummary]:
187
188
  """Run the full extraction pipeline.
188
189
 
@@ -200,9 +201,14 @@ class ContractPipeline:
200
201
  engine = RankingEngine(monorepo_packages)
201
202
 
202
203
  # 1. Changed files (for --changed-only and ranking)
204
+ # When allowed_changed_files is provided (from pre-computed git uncommitted_changes),
205
+ # use it directly. Otherwise fall back to independent git commands.
203
206
  changed_files: set[str] = set()
204
207
  if changed_only or rank_by == "git-churn":
205
- changed_files = _get_changed_files(root)
208
+ if allowed_changed_files is not None:
209
+ changed_files = allowed_changed_files
210
+ else:
211
+ changed_files = _get_changed_files(root)
206
212
 
207
213
  # 2. Select files to extract
208
214
  # Exclude test files by default — they dominate by count but add noise
@@ -398,11 +398,27 @@ class TaskContextBuilder:
398
398
  from sourcecode.tree_utils import flatten_file_tree
399
399
 
400
400
  _topology = RepoClassifier().classify(self.root)
401
- scanner = AdaptiveScanner(self.root, topology=_topology, base_depth=6)
401
+ # Shallow pre-scan to detect Java manifests before choosing depth.
402
+ from sourcecode.scanner import FileScanner as _FileScanner
403
+ _pre = _FileScanner(self.root, max_depth=1)
404
+ _pre_manifests = _pre.find_manifests()
405
+ _java_names = {"pom.xml", "build.gradle", "build.gradle.kts"}
406
+ _is_java = any(Path(m).name in _java_names for m in _pre_manifests)
407
+ _base_depth = 12 if _is_java else 6
408
+ scanner = AdaptiveScanner(self.root, topology=_topology, base_depth=_base_depth)
402
409
  file_tree = scanner.scan_tree()
403
410
  manifests = scanner.find_manifests()
404
411
  all_paths = [p.replace("\\", "/") for p in flatten_file_tree(file_tree)]
405
412
 
413
+ # Warn when Java project has no Mapper.xml — suggests files below scan depth.
414
+ _mybatis_warning: dict | None = None
415
+ if _is_java and not any(p.endswith("Mapper.xml") for p in all_paths):
416
+ _mybatis_warning = {
417
+ "area": "mybatis",
418
+ "reason": "Mapper XML files may exist below scan depth. Re-run with --depth 12.",
419
+ "impact": "high",
420
+ }
421
+
406
422
  # ── 2. Detect stacks + entry points ───────────────────────────────
407
423
  from dataclasses import replace as _replace
408
424
  from sourcecode.detectors import ProjectDetector, build_default_detectors
@@ -500,6 +516,7 @@ class TaskContextBuilder:
500
516
  code_notes_summary: Optional[dict[str, Any]] = None
501
517
  suspected_areas: list[str] = []
502
518
  improvement_opportunities: list[str] = []
519
+ cn_notes_for_ranking: list = []
503
520
 
504
521
  if spec.enable_code_notes:
505
522
  from dataclasses import asdict
@@ -507,6 +524,7 @@ class TaskContextBuilder:
507
524
 
508
525
  cn_notes, _cn_adrs, cn_summary = CodeNotesAnalyzer().analyze(self.root)
509
526
  code_notes_summary = asdict(cn_summary)
527
+ cn_notes_for_ranking = cn_notes
510
528
 
511
529
  if task_name == "fix-bug":
512
530
  bug_kinds = {"FIXME", "BUG", "HACK", "XXX"}
@@ -555,6 +573,7 @@ class TaskContextBuilder:
555
573
  monorepo_packages=sm.monorepo_packages if sm.monorepo_packages else None,
556
574
  git_hotspots=git_hotspots,
557
575
  uncommitted_files=uncommitted_files,
576
+ code_notes=cn_notes_for_ranking if cn_notes_for_ranking else None,
558
577
  )
559
578
 
560
579
  # ── 7. Test gaps (generate-tests only) ────────────────────────────
@@ -594,6 +613,8 @@ class TaskContextBuilder:
594
613
  conf_summary, analysis_gaps = ConfidenceAnalyzer().analyze(sm_for_conf)
595
614
  confidence = conf_summary.overall
596
615
  gaps = [g.reason for g in analysis_gaps]
616
+ if _mybatis_warning:
617
+ gaps.append(_mybatis_warning["reason"])
597
618
 
598
619
  # ── 9. why_these_files ────────────────────────────────────────────────
599
620
  why_these_files: dict[str, str] = {
@@ -705,6 +726,7 @@ class TaskContextBuilder:
705
726
  monorepo_packages: Optional[list] = None,
706
727
  git_hotspots: Optional[dict[str, int]] = None,
707
728
  uncommitted_files: Optional[set[str]] = None,
729
+ code_notes: Optional[list] = None,
708
730
  ) -> list[RelevantFile]:
709
731
  from sourcecode.ranking_engine import RankingEngine
710
732
  from sourcecode.file_classifier import FileClassifier
@@ -719,6 +741,35 @@ class TaskContextBuilder:
719
741
  _uncommitted = uncommitted_files or set()
720
742
  _max_churn = max(_hotspots.values(), default=1)
721
743
 
744
+ # Pre-compute fix-bug signals (used only when task_name == "fix-bug")
745
+ _annotated_files: set[str] = set()
746
+ _dominant_stack = ""
747
+ _recently_changed_stacks: set[str] = set()
748
+ if task_name == "fix-bug":
749
+ _bug_kinds = {"FIXME", "BUG"}
750
+ for _n in (code_notes or []):
751
+ if getattr(_n, "kind", "").upper() in _bug_kinds:
752
+ _annotated_files.add(getattr(_n, "path", ""))
753
+
754
+ def _file_stack(p: str) -> str:
755
+ ext = Path(p).suffix.lower()
756
+ if ext == ".java": return "java"
757
+ if ext in (".ts", ".tsx", ".js", ".jsx", ".mjs"): return "typescript"
758
+ if ext == ".py": return "python"
759
+ if ext == ".go": return "go"
760
+ if ext in (".kt", ".kts"): return "kotlin"
761
+ if ext == ".rs": return "rust"
762
+ if ext == ".rb": return "ruby"
763
+ return "unknown"
764
+
765
+ from collections import Counter as _Counter
766
+ _stk_counts: _Counter[str] = _Counter(
767
+ _file_stack(f) for f in _uncommitted if _file_stack(f) != "unknown"
768
+ )
769
+ if _stk_counts:
770
+ _dominant_stack = _stk_counts.most_common(1)[0][0]
771
+ _recently_changed_stacks = set(_stk_counts.keys())
772
+
722
773
  scored: list[tuple[float, str, RelevantFile]] = []
723
774
 
724
775
  for path in all_paths:
@@ -762,10 +813,28 @@ class TaskContextBuilder:
762
813
 
763
814
  # Task-specific boosts for differentiated file weighting
764
815
  path_lower = path.lower()
816
+ _fix_bug_why = ""
765
817
  if task_name == "fix-bug":
766
- if any(x in path_lower for x in ("exception", "error", "handler", "advice")):
767
- content_boost += 1.5
768
- content_reasons.append("exception handler — high risk area")
818
+ _why_parts: list[str] = []
819
+ if path in _uncommitted:
820
+ content_boost += 0.40
821
+ _why_parts.append("uncommitted change (+0.40)")
822
+ _recency = min(0.30, _hotspots.get(path, 0) * 0.05)
823
+ if _recency > 0:
824
+ content_boost += _recency
825
+ _why_parts.append(f"recent commits (+{_recency:.2f})")
826
+ if path in _annotated_files:
827
+ content_boost += 0.20
828
+ _why_parts.append("FIXME/BUG annotation (+0.20)")
829
+ _file_stk = _file_stack(path)
830
+ if _dominant_stack and _file_stk == _dominant_stack:
831
+ content_boost += 0.10
832
+ _why_parts.append("dominant changed stack (+0.10)")
833
+ if _recently_changed_stacks and _file_stk not in _recently_changed_stacks and _file_stk != "unknown":
834
+ content_boost -= 0.30
835
+ _why_parts.append("different stack from recent changes (-0.30)")
836
+ if _why_parts:
837
+ _fix_bug_why = ", ".join(_why_parts)
769
838
  elif task_name == "generate-tests":
770
839
  stem = Path(path).stem.lower()
771
840
  has_test = any(
@@ -797,7 +866,7 @@ class TaskContextBuilder:
797
866
  )
798
867
  all_reasons = [r for r in fs.reasons if r != "source file"] + content_reasons
799
868
  reason_str = ", ".join(all_reasons) if all_reasons else "source file"
800
- why_str = _java_why(path, file_class)
869
+ why_str = _fix_bug_why if _fix_bug_why else _java_why(path, file_class)
801
870
 
802
871
  scored.append((total, path, RelevantFile(
803
872
  path=path,