sourcecode 1.31.11__tar.gz → 1.31.13__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 (238) hide show
  1. {sourcecode-1.31.11 → sourcecode-1.31.13}/PKG-INFO +3 -3
  2. {sourcecode-1.31.11 → sourcecode-1.31.13}/README.md +2 -2
  3. {sourcecode-1.31.11 → sourcecode-1.31.13}/pyproject.toml +1 -1
  4. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/cli.py +289 -21
  6. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/server.py +67 -11
  7. sourcecode-1.31.13/src/sourcecode/output_budget.py +157 -0
  8. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/prepare_context.py +16 -23
  9. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/repository_ir.py +604 -20
  10. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/serializer.py +41 -4
  11. sourcecode-1.31.13/tests/test_audit_fixes.py +833 -0
  12. sourcecode-1.31.13/tests/test_audit_sas_v2.py +506 -0
  13. sourcecode-1.31.13/tests/test_enterprise_benchmarks.py +967 -0
  14. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_repository_ir.py +327 -0
  15. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_v1_10_regressions.py +119 -7
  16. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-0911b79e.json +0 -3825
  17. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-37df4554.json +0 -266
  18. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-624321f3.json +0 -12478
  19. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-776b4676.json +0 -386
  20. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-9770fba7.json +0 -377
  21. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-c4e3c102.json +0 -266
  22. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-e8bc5fb4.json +0 -122
  23. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-e9801942.json +0 -223
  24. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-ee60e0cd.json +0 -322
  25. sourcecode-1.31.11/.sourcecode-cache/snapshot-0778d0a-fdd9d3f7.json +0 -552
  26. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-27b713ac.json +0 -386
  27. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-28e558ac.json +0 -325
  28. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-2d7eca5f.json +0 -12577
  29. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-7cd91058.json +0 -226
  30. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-8ea5b5bb.json +0 -380
  31. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-bef1a526.json +0 -261
  32. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-c69bcdc8.json +0 -122
  33. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-d2dfc690.json +0 -3825
  34. sourcecode-1.31.11/.sourcecode-cache/snapshot-3b5997a-dc30daab.json +0 -552
  35. {sourcecode-1.31.11 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  36. {sourcecode-1.31.11 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  37. {sourcecode-1.31.11 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  38. {sourcecode-1.31.11 → sourcecode-1.31.13}/.continue-here.md +0 -0
  39. {sourcecode-1.31.11 → sourcecode-1.31.13}/.github/workflows/build-windows.yml +0 -0
  40. {sourcecode-1.31.11 → sourcecode-1.31.13}/.gitignore +0 -0
  41. {sourcecode-1.31.11 → sourcecode-1.31.13}/.ruff.toml +0 -0
  42. {sourcecode-1.31.11 → sourcecode-1.31.13}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
  43. {sourcecode-1.31.11 → sourcecode-1.31.13}/CHANGELOG.md +0 -0
  44. {sourcecode-1.31.11 → sourcecode-1.31.13}/CONTRIBUTING.md +0 -0
  45. {sourcecode-1.31.11 → sourcecode-1.31.13}/LICENSE +0 -0
  46. {sourcecode-1.31.11 → sourcecode-1.31.13}/SECURITY.md +0 -0
  47. {sourcecode-1.31.11 → sourcecode-1.31.13}/docs/privacy.md +0 -0
  48. {sourcecode-1.31.11 → sourcecode-1.31.13}/docs/schema.md +0 -0
  49. {sourcecode-1.31.11 → sourcecode-1.31.13}/raw +0 -0
  50. {sourcecode-1.31.11 → sourcecode-1.31.13}/run_cli.py +0 -0
  51. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/adaptive_scanner.py +0 -0
  52. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/architecture_analyzer.py +0 -0
  53. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/architecture_summary.py +0 -0
  54. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/ast_extractor.py +0 -0
  55. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/classifier.py +0 -0
  56. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/code_notes_analyzer.py +0 -0
  57. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/confidence_analyzer.py +0 -0
  58. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/context_scorer.py +0 -0
  59. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/context_summarizer.py +0 -0
  60. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/contract_model.py +0 -0
  61. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/contract_pipeline.py +0 -0
  62. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/coverage_parser.py +0 -0
  63. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/dependency_analyzer.py +0 -0
  64. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/__init__.py +0 -0
  65. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/base.py +0 -0
  66. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/csproj_parser.py +0 -0
  67. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/dart.py +0 -0
  68. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/dotnet.py +0 -0
  69. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/elixir.py +0 -0
  70. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/go.py +0 -0
  71. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/heuristic.py +0 -0
  72. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/hybrid.py +0 -0
  73. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/java.py +0 -0
  74. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/jvm_ext.py +0 -0
  75. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/nodejs.py +0 -0
  76. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/parsers.py +0 -0
  77. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/php.py +0 -0
  78. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/project.py +0 -0
  79. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/python.py +0 -0
  80. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/ruby.py +0 -0
  81. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/rust.py +0 -0
  82. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/systems.py +0 -0
  83. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/terraform.py +0 -0
  84. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/detectors/tooling.py +0 -0
  85. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/doc_analyzer.py +0 -0
  86. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/entrypoint_classifier.py +0 -0
  87. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/env_analyzer.py +0 -0
  88. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/file_classifier.py +0 -0
  89. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/flow_analyzer.py +0 -0
  90. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/git_analyzer.py +0 -0
  91. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/graph_analyzer.py +0 -0
  92. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/__init__.py +0 -0
  93. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  94. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  95. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  96. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  97. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  98. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/mcp/runner.py +0 -0
  99. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/metrics_analyzer.py +0 -0
  100. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/path_filters.py +0 -0
  101. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/pr_comment_renderer.py +0 -0
  102. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/progress.py +0 -0
  103. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/ranking_engine.py +0 -0
  104. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/redactor.py +0 -0
  105. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/relevance_scorer.py +0 -0
  106. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/repo_classifier.py +0 -0
  107. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/runtime_classifier.py +0 -0
  108. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/scanner.py +0 -0
  109. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/schema.py +0 -0
  110. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/semantic_analyzer.py +0 -0
  111. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/summarizer.py +0 -0
  112. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/__init__.py +0 -0
  113. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/config.py +0 -0
  114. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/consent.py +0 -0
  115. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/events.py +0 -0
  116. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/filters.py +0 -0
  117. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/telemetry/transport.py +0 -0
  118. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/tree_utils.py +0 -0
  119. {sourcecode-1.31.11 → sourcecode-1.31.13}/src/sourcecode/workspace.py +0 -0
  120. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/__init__.py +0 -0
  121. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/conftest.py +0 -0
  122. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/coverage.xml +0 -0
  123. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  124. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/fastapi_app/src/main.py +0 -0
  125. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  126. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/go_service/go.mod +0 -0
  127. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/jacoco.xml +0 -0
  128. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/latin1_sample.java +0 -0
  129. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/latin1_sample_iso.java +0 -0
  130. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/lcov.info +0 -0
  131. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  132. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/package.json +0 -0
  133. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  134. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  135. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  136. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  137. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  138. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  139. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  140. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  141. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  142. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  143. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  144. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  145. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  146. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  147. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  148. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  149. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  150. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  151. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  152. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  153. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  154. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  155. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  156. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  157. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  158. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  159. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  160. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  161. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  162. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  163. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  164. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  165. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  166. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_architecture_analyzer.py +0 -0
  167. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_architecture_summary.py +0 -0
  168. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_ast_extractor.py +0 -0
  169. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_block1_reliability.py +0 -0
  170. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_block2_coverage.py +0 -0
  171. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_block5_quality.py +0 -0
  172. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_broadleaf_fixes.py +0 -0
  173. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_bug_fixes_v1302.py +0 -0
  174. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_bug_fixes_v1312.py +0 -0
  175. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_bug_fixes_v1313.py +0 -0
  176. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_bug_fixes_v16.py +0 -0
  177. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_bug_fixes_v2.py +0 -0
  178. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_classifier.py +0 -0
  179. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_cli.py +0 -0
  180. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_code_notes_analyzer.py +0 -0
  181. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_context_scorer.py +0 -0
  182. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_contract_pipeline.py +0 -0
  183. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_coverage_parser.py +0 -0
  184. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_cross_consistency.py +0 -0
  185. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_dependency_analyzer_node_python.py +0 -0
  186. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_dependency_analyzer_polyglot.py +0 -0
  187. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_dependency_schema.py +0 -0
  188. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_dotnet.py +0 -0
  189. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_go_rust_java.py +0 -0
  190. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_nodejs.py +0 -0
  191. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_php_ruby_dart.py +0 -0
  192. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_python.py +0 -0
  193. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_universal_managed.py +0 -0
  194. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detector_universal_systems.py +0 -0
  195. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_detectors_base.py +0 -0
  196. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_doc_analyzer_jsdom.py +0 -0
  197. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_doc_analyzer_python.py +0 -0
  198. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_encoding_regression.py +0 -0
  199. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_graph_analyzer_polyglot.py +0 -0
  200. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_graph_analyzer_python_node.py +0 -0
  201. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_graph_schema.py +0 -0
  202. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_hybrid_inference.py +0 -0
  203. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration.py +0 -0
  204. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_dependencies.py +0 -0
  205. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_detection.py +0 -0
  206. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_docs.py +0 -0
  207. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_graph_modules.py +0 -0
  208. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_lqn.py +0 -0
  209. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_metrics.py +0 -0
  210. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_multistack.py +0 -0
  211. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_semantics.py +0 -0
  212. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_integration_universal.py +0 -0
  213. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_java_spring_integration.py +0 -0
  214. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_mcp_runner.py +0 -0
  215. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_mcp_serve.py +0 -0
  216. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_mcp_tools.py +0 -0
  217. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_metrics_analyzer.py +0 -0
  218. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_output_ux.py +0 -0
  219. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_packaging.py +0 -0
  220. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_phase1_improvements.py +0 -0
  221. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_pipeline_integrity.py +0 -0
  222. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_real_projects.py +0 -0
  223. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_redactor.py +0 -0
  224. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_scanner.py +0 -0
  225. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_schema.py +0 -0
  226. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_schema_normalization.py +0 -0
  227. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_scoring_calibration.py +0 -0
  228. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_semantic_analyzer_node.py +0 -0
  229. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_semantic_analyzer_python.py +0 -0
  230. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_semantic_import_resolution.py +0 -0
  231. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_semantic_schema.py +0 -0
  232. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_signal_hierarchy.py +0 -0
  233. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_summarizer.py +0 -0
  234. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_surface_honesty.py +0 -0
  235. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_task_differentiation.py +0 -0
  236. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_telemetry.py +0 -0
  237. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_v131_improvements.py +0 -0
  238. {sourcecode-1.31.11 → sourcecode-1.31.13}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.31.11
3
+ Version: 1.31.13
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
225
225
 
226
226
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
227
227
 
228
- ![Version](https://img.shields.io/badge/version-1.31.11-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.13-blue)
229
229
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
230
230
 
231
231
  ---
@@ -261,7 +261,7 @@ pipx install sourcecode
261
261
 
262
262
  ```bash
263
263
  sourcecode version
264
- # sourcecode 1.31.11
264
+ # sourcecode 1.31.13
265
265
  ```
266
266
 
267
267
  ---
@@ -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.31.11-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.31.13-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.31.11
41
+ # sourcecode 1.31.13
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.31.11"
7
+ version = "1.31.13"
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.31.11"
3
+ __version__ = "1.31.13"
@@ -166,7 +166,7 @@ Compressed AI-ready context for Java/Spring enterprise codebases.
166
166
  # Known subcommand names — tokens matching these are routed as subcommands,
167
167
  # not consumed as a repository path.
168
168
  _SUBCOMMANDS: frozenset[str] = frozenset(
169
- {"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp", "endpoints"}
169
+ {"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp", "endpoints", "impact"}
170
170
  )
171
171
 
172
172
  # Mutable container holding the path extracted by _preprocess_argv().
@@ -671,14 +671,14 @@ def main(
671
671
  f"Error: invalid value '{mode}' for --mode. Valid options: {', '.join(_MODE_CHOICES)}",
672
672
  err=True,
673
673
  )
674
- raise typer.Exit(code=1)
674
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
675
675
  _RANK_CHOICES = ("relevance", "centrality", "git-churn")
676
676
  if rank_by not in _RANK_CHOICES:
677
677
  typer.echo(
678
678
  f"Error: invalid value '{rank_by}' for --rank-by. Valid options: {', '.join(_RANK_CHOICES)}",
679
679
  err=True,
680
680
  )
681
- raise typer.Exit(code=1)
681
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
682
682
 
683
683
  if symbol is not None and not symbol.strip():
684
684
  typer.echo("symbol query cannot be empty", err=True)
@@ -690,14 +690,14 @@ def main(
690
690
  "Symbol search uses the contract pipeline which does not run in raw mode.",
691
691
  err=True,
692
692
  )
693
- raise typer.Exit(code=1)
693
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
694
694
 
695
695
  if entrypoints_only and mode not in ("contract", "standard"):
696
696
  typer.echo(
697
697
  f"Error: --entrypoints-only requires --mode contract or standard (got '{mode}').",
698
698
  err=True,
699
699
  )
700
- raise typer.Exit(code=1)
700
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
701
701
 
702
702
  if dependency_depth > 0:
703
703
  typer.echo(
@@ -715,33 +715,65 @@ def main(
715
715
  err=True,
716
716
  )
717
717
 
718
+ # P0-2 FIX: --compact and --full are mutually exclusive.
719
+ # compact is designed to be a bounded summary; --full removes truncation limits,
720
+ # which contradicts compact's purpose. Use --agent --full for expanded output.
721
+ if compact and full:
722
+ typer.echo(
723
+ "Error: --compact and --full are mutually exclusive. "
724
+ "--compact produces a bounded summary; --full removes truncation limits and "
725
+ "is meant for --agent mode. Use --agent --full for expanded output.",
726
+ err=True,
727
+ )
728
+ raise typer.Exit(code=2)
729
+
730
+ # P0-2 FIX: --full without --compact or --agent has no effect in contract/raw mode.
731
+ # Warn so the user knows the flag is not doing anything.
732
+ if full and not compact and not agent:
733
+ typer.echo(
734
+ "[warning] --full has no effect in contract/raw mode. "
735
+ "It only expands mybatis.dto_mappers and transactional_boundaries in "
736
+ "--compact or --agent mode. Add --agent to get expanded output.",
737
+ err=True,
738
+ )
739
+
740
+ # P0-2 FIX: --changed-only silently implies --compact; inform the user.
741
+ if changed_only and not compact and not agent:
742
+ typer.echo(
743
+ "[info] --changed-only implies --compact (bounding output to changed files).",
744
+ err=True,
745
+ )
746
+
718
747
  # Validate format choices
719
748
  if format not in FORMAT_CHOICES:
720
749
  typer.echo(
721
750
  f"Error: invalid value '{format}' for --format. Valid options: {', '.join(FORMAT_CHOICES)}",
722
751
  err=True,
723
752
  )
724
- raise typer.Exit(code=1)
753
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
725
754
  if graph_detail not in GRAPH_DETAIL_CHOICES:
726
755
  typer.echo(
727
756
  f"Error: invalid value '{graph_detail}' for --graph-detail. Valid options: {', '.join(GRAPH_DETAIL_CHOICES)}",
728
757
  err=True,
729
758
  )
730
- raise typer.Exit(code=1)
759
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
731
760
  if docs_depth not in DOCS_DEPTH_CHOICES:
732
761
  typer.echo(
733
762
  f"Error: invalid value '{docs_depth}' for --docs-depth. Valid options: {', '.join(DOCS_DEPTH_CHOICES)}",
734
763
  err=True,
735
764
  )
736
- raise typer.Exit(code=1)
765
+ raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
737
766
 
738
767
  # Path was extracted from argv by _preprocess_argv() before Click ran.
739
- target = Path(_detected_path[0]).resolve()
768
+ # FIX-P2-8: preserve original user input in error messages (Windows Git Bash
769
+ # rewrites "/nonexistent" → "C:\Program Files\Git\nonexistent" via Path.resolve()).
770
+ _raw_path_input = _detected_path[0]
771
+ target = Path(_raw_path_input).resolve()
740
772
  if not target.exists():
741
- typer.echo(f"Error: directory '{target}' does not exist.", err=True)
773
+ typer.echo(f"Error: directory '{_raw_path_input}' does not exist.", err=True)
742
774
  raise typer.Exit(code=1)
743
775
  if not target.is_dir():
744
- typer.echo(f"Error: '{target}' is not a directory.", err=True)
776
+ typer.echo(f"Error: '{_raw_path_input}' is not a directory.", err=True)
745
777
  raise typer.Exit(code=1)
746
778
 
747
779
  # Normalize mode aliases
@@ -860,13 +892,27 @@ def main(
860
892
  # Include every output-affecting flag so different flag combos never collide
861
893
  # Include version so cache is invalidated on sourcecode upgrades
862
894
  from sourcecode import __version__ as _sc_version
895
+ # FIX-P0-1: cache key must include ALL analysis-affecting flags.
896
+ # Previously missing: exclude, depth, rank_by, symbol, entrypoints_only,
897
+ # no_redact, graph_detail, docs_depth, max_nodes, graph_edges,
898
+ # max_importers, emit_graph.
899
+ # Use effective_depth (not raw depth) so Java auto-adjustment is captured.
900
+ _excl_key = (
901
+ ",".join(sorted(e.strip() for e in exclude.split(",") if e.strip()))
902
+ if exclude else ""
903
+ )
863
904
  _flags_str = (
864
905
  f"v={_sc_version},"
865
906
  f"c={compact},ag={agent},fmt={format},full={full},"
866
907
  f"co={changed_only},dep={dependencies},gm={graph_modules},"
867
908
  f"docs={docs},fm={full_metrics},sem={semantics},"
868
909
  f"arch={architecture},gc={git_context},em={env_map},"
869
- f"cn={code_notes},tree={tree},mode={mode}"
910
+ f"cn={code_notes},tree={tree},mode={mode},"
911
+ f"ex={_excl_key},depth={effective_depth},"
912
+ f"rb={rank_by},sym={symbol},ep={entrypoints_only},"
913
+ f"nr={no_redact},gd={graph_detail},dd={docs_depth},"
914
+ f"mn={max_nodes},ge={graph_edges},mi={max_importers},"
915
+ f"eg={emit_graph}"
870
916
  )
871
917
  _flags_h = _hashlib.md5(_flags_str.encode()).hexdigest()[:8]
872
918
  _cache_key = f"{_git_sha}-{_flags_h}"
@@ -1612,7 +1658,26 @@ def main(
1612
1658
  data = agent_view(sm, full=full)
1613
1659
  if not no_redact:
1614
1660
  data = redact_dict(data)
1615
- content = json.dumps(data, indent=2, ensure_ascii=False)
1661
+ # P0-1: Apply output budget — safety net for large repos.
1662
+ from sourcecode.output_budget import trim_to_budget as _trim, BUDGET_AGENT
1663
+ data = _trim(data, BUDGET_AGENT, label="agent")
1664
+ # FIX-P0-2: agent mode must honour --format yaml (previously always emitted JSON).
1665
+ if format == "yaml":
1666
+ from io import StringIO
1667
+ from ruamel.yaml import YAML as _YAML
1668
+ _yaml_ag = _YAML()
1669
+ _yaml_ag.default_flow_style = False
1670
+ _yaml_ag.representer.add_representer(
1671
+ type(None),
1672
+ lambda dumper, data_val: dumper.represent_scalar(
1673
+ "tag:yaml.org,2002:null", "null"
1674
+ ),
1675
+ )
1676
+ _stream_ag = StringIO()
1677
+ _yaml_ag.dump(data, _stream_ag)
1678
+ content = _stream_ag.getvalue()
1679
+ else:
1680
+ content = json.dumps(data, indent=2, ensure_ascii=False)
1616
1681
  elif compact:
1617
1682
  if changed_only and _allowed_changed_files:
1618
1683
  # GAP-5: preserve full entry_points for architecture context even in
@@ -1624,6 +1689,9 @@ def main(
1624
1689
  data = compact_view(sm, no_tree=no_tree, full=full)
1625
1690
  if not no_redact:
1626
1691
  data = redact_dict(data)
1692
+ # P0-1: Apply output budget — safety net for large repos.
1693
+ from sourcecode.output_budget import trim_to_budget as _trim_c, BUDGET_COMPACT
1694
+ data = _trim_c(data, BUDGET_COMPACT, label="compact")
1627
1695
  if format == "yaml":
1628
1696
  from io import StringIO
1629
1697
  from ruamel.yaml import YAML as _YAML
@@ -2158,7 +2226,10 @@ def prepare_context_cmd(
2158
2226
  _sys.stdout.buffer.write(_err_json.encode("utf-8"))
2159
2227
  _sys.stdout.buffer.write(b"\n")
2160
2228
  _sys.stdout.buffer.flush()
2161
- raise typer.Exit(code=1)
2229
+ # FIX: no_diff (no PR changes) is not an error — exit 0, consistent
2230
+ # with delta's no_changes handling. Only true errors exit non-zero.
2231
+ _review_pr_exit = 0 if output.error_code == "no_diff" else 1
2232
+ raise typer.Exit(code=_review_pr_exit)
2162
2233
  out["review_type"] = "pull_request"
2163
2234
  if output.ci_decision:
2164
2235
  out["ci_decision"] = output.ci_decision
@@ -2240,6 +2311,24 @@ def prepare_context_cmd(
2240
2311
  if llm_prompt:
2241
2312
  out["llm_prompt"] = builder.render_prompt(output)
2242
2313
 
2314
+ # P0-1: Apply output budget per task — safety net for large repos.
2315
+ from sourcecode.output_budget import (
2316
+ trim_to_budget as _pc_trim,
2317
+ BUDGET_FIX_BUG, BUDGET_REVIEW_PR, BUDGET_ONBOARD,
2318
+ BUDGET_EXPLAIN, BUDGET_REFACTOR, BUDGET_DELTA,
2319
+ )
2320
+ _pc_budgets: dict[str, int] = {
2321
+ "fix-bug": BUDGET_FIX_BUG,
2322
+ "review-pr": BUDGET_REVIEW_PR,
2323
+ "onboard": BUDGET_ONBOARD,
2324
+ "explain": BUDGET_EXPLAIN,
2325
+ "refactor": BUDGET_REFACTOR,
2326
+ "delta": BUDGET_DELTA,
2327
+ "generate-tests": BUDGET_EXPLAIN,
2328
+ }
2329
+ _pc_budget = _pc_budgets.get(task, BUDGET_EXPLAIN)
2330
+ out = _pc_trim(out, _pc_budget, label=task)
2331
+
2243
2332
  if format == "github-comment" and task == "review-pr":
2244
2333
  from sourcecode.pr_comment_renderer import render_github_comment
2245
2334
  _pc_content = render_github_comment(out)
@@ -2457,6 +2546,115 @@ def repo_ir_cmd(
2457
2546
  _sys.stdout.write("\n")
2458
2547
 
2459
2548
 
2549
+ # ── impact (blast-radius / change-impact analysis) ────────────────────────────
2550
+
2551
+ @app.command("impact")
2552
+ def impact_cmd(
2553
+ target: str = typer.Argument(
2554
+ ...,
2555
+ help=(
2556
+ "Class name (simple or FQN) or file path to analyze impact for. "
2557
+ "Examples: UserService, org.example.UserService, UserService.java"
2558
+ ),
2559
+ ),
2560
+ path: Path = typer.Argument(
2561
+ Path("."),
2562
+ help="Repository root to analyze (default: current directory)",
2563
+ ),
2564
+ depth: int = typer.Option(
2565
+ 4,
2566
+ "--depth",
2567
+ help="BFS depth for indirect caller traversal (default: 4).",
2568
+ min=1,
2569
+ max=8,
2570
+ ),
2571
+ output_path: Optional[Path] = typer.Option(
2572
+ None,
2573
+ "--output",
2574
+ "-o",
2575
+ help="Write output to a file instead of stdout.",
2576
+ ),
2577
+ include_tests: bool = typer.Option(
2578
+ False,
2579
+ "--include-tests",
2580
+ help="Include test files in analysis (excluded by default).",
2581
+ ),
2582
+ ) -> None:
2583
+ """Blast-radius analysis: who calls this class and what breaks if it changes?
2584
+
2585
+ \b
2586
+ Builds the repository IR and propagates impact from the target symbol
2587
+ through the reverse dependency graph. Returns:
2588
+ - direct_callers — classes that directly call or depend on the target
2589
+ - indirect_callers — transitive callers (BFS, bounded by --depth)
2590
+ - endpoints_affected — HTTP endpoints that transitively depend on the target
2591
+ - transactional_boundaries_touched — @Transactional classes in the call chain
2592
+ - risk_score / risk_level — quantified change risk
2593
+
2594
+ \b
2595
+ Examples:
2596
+ sourcecode impact UserService
2597
+ sourcecode impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
2598
+ sourcecode impact UserService --depth 6 --output impact.json
2599
+ """
2600
+ import json as _json
2601
+ import sys as _sys
2602
+
2603
+ from sourcecode.repository_ir import (
2604
+ build_repo_ir, find_java_files, compute_blast_radius,
2605
+ )
2606
+ from sourcecode.output_budget import trim_to_budget as _trim, BUDGET_IMPACT
2607
+
2608
+ root = path.resolve()
2609
+ if not root.is_dir():
2610
+ typer.echo(f"Error: {root} is not a directory", err=True)
2611
+ raise typer.Exit(1)
2612
+
2613
+ file_list = find_java_files(root)
2614
+ if not include_tests:
2615
+ file_list = [f for f in file_list if "/test/" not in f and "/tests/" not in f]
2616
+
2617
+ if not file_list:
2618
+ typer.echo(
2619
+ _json.dumps(
2620
+ {
2621
+ "target": target,
2622
+ "resolution": "not_found",
2623
+ "message": "No Java files found in repository.",
2624
+ "risk_level": "unknown",
2625
+ },
2626
+ indent=2,
2627
+ )
2628
+ )
2629
+ return
2630
+
2631
+ _prog = Progress()
2632
+ _prog.start(f"building IR ({len(file_list)} files) for impact analysis")
2633
+ try:
2634
+ ir = build_repo_ir(file_list, root)
2635
+ finally:
2636
+ _prog.finish()
2637
+
2638
+ result = compute_blast_radius(ir, target, max_depth=depth)
2639
+ result = _trim(result, BUDGET_IMPACT, label="impact")
2640
+
2641
+ output = _json.dumps(result, indent=2, ensure_ascii=False)
2642
+ if output_path:
2643
+ output_path.write_text(output, encoding="utf-8")
2644
+ typer.echo(f"Impact analysis written to {output_path}", err=True)
2645
+ else:
2646
+ try:
2647
+ _sys.stdout.buffer.write(output.encode("utf-8"))
2648
+ _sys.stdout.buffer.write(b"\n")
2649
+ _sys.stdout.buffer.flush()
2650
+ except AttributeError:
2651
+ _sys.stdout.write(output + "\n")
2652
+
2653
+ # Non-zero exit when target not found
2654
+ if result.get("resolution") == "not_found":
2655
+ raise typer.Exit(code=1)
2656
+
2657
+
2460
2658
  # ── endpoints ─────────────────────────────────────────────────────────────────
2461
2659
 
2462
2660
  # _extract_java_endpoints is imported from sourcecode.repository_ir as the
@@ -2723,6 +2921,9 @@ def mcp_init(
2723
2921
  @mcp_app.command("status")
2724
2922
  def mcp_status() -> None:
2725
2923
  """Show MCP integration status: dependencies, config files, and connectivity."""
2924
+ import subprocess as _sp
2925
+ import sys as _sys
2926
+ from sourcecode import __version__ as _cli_version
2726
2927
  from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
2727
2928
  from sourcecode.mcp.onboarding import applier
2728
2929
 
@@ -2731,6 +2932,10 @@ def mcp_status() -> None:
2731
2932
  typer.echo("MCP Status")
2732
2933
  typer.echo(sep)
2733
2934
 
2935
+ # FIX-P0-5/P0-6: Show CLI version explicitly so drift is immediately visible.
2936
+ typer.echo(f"CLI version {_cli_version} ({_sys.executable})")
2937
+ typer.echo("")
2938
+
2734
2939
  # Stage 1: Dependencies
2735
2940
  try:
2736
2941
  import mcp as _mcp_pkg # noqa: F401
@@ -2747,27 +2952,88 @@ def mcp_status() -> None:
2747
2952
  typer.echo(" Setup: sourcecode mcp init")
2748
2953
  raise typer.Exit(code=0)
2749
2954
 
2750
- # Stage 2: Config files
2751
- typer.echo("Config files")
2955
+ # Stage 2: Config files — is sourcecode registered in the client's config?
2956
+ # FIX-P0-6: "configured" and "running" are distinct, independent checks.
2957
+ # Also detect external server installs (different Python/executable than CLI).
2958
+ typer.echo("Config (sourcecode registered in client config?)")
2752
2959
  for client in clients:
2753
2960
  if not client.app_installed:
2754
- typer.echo(f" {client.name:<20} ✗ not found")
2961
+ typer.echo(f" {client.name:<20} ✗ app not found at expected path")
2755
2962
  typer.echo(f" Expected: {client.config_path}")
2756
2963
  typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
2757
2964
  continue
2758
2965
  config = applier.read_config(client.config_path)
2759
2966
  if applier.is_installed(config):
2760
2967
  typer.echo(f" {client.name:<20} ✓ configured {client.config_path}")
2968
+ # FIX-P0-5: inspect registered command for external-server drift.
2969
+ _registered = config.get("mcpServers", {}).get("sourcecode", {})
2970
+ _reg_cmd = _registered.get("command", "")
2971
+ _reg_args = _registered.get("args", [])
2972
+ # Built-in form: command=sourcecode args=[mcp, serve] (or just the binary)
2973
+ _is_builtin = (
2974
+ _reg_cmd == "sourcecode"
2975
+ or (not _reg_args and _reg_cmd.endswith("/sourcecode"))
2976
+ or (_reg_args and _reg_args[:2] == ["mcp", "serve"])
2977
+ )
2978
+ if _is_builtin:
2979
+ typer.echo(f" Server: built-in (sourcecode mcp serve) version={_cli_version}")
2980
+ else:
2981
+ # External server — different Python or custom server.py
2982
+ typer.echo(f" Server: ⚠ EXTERNAL — {_reg_cmd} {' '.join(_reg_args)}")
2983
+ # Try to get the external server's sourcecode version by finding the
2984
+ # sourcecode binary relative to the registered Python executable,
2985
+ # or falling back to probing the Python for the installed package.
2986
+ _ext_ver: str = "unknown"
2987
+ try:
2988
+ import os as _os
2989
+ # Strategy 1: look for sourcecode binary next to registered Python
2990
+ _reg_bin_dir = _os.path.dirname(_reg_cmd)
2991
+ _sc_sibling = _os.path.join(_reg_bin_dir, "sourcecode")
2992
+ if _os.path.isfile(_sc_sibling):
2993
+ _ver_r = _sp.run(
2994
+ [_sc_sibling, "--version"],
2995
+ capture_output=True, text=True, timeout=5,
2996
+ )
2997
+ if _ver_r.returncode == 0 and _ver_r.stdout.strip():
2998
+ # "sourcecode X.Y.Z" → extract version
2999
+ _ext_ver = _ver_r.stdout.strip().split()[-1]
3000
+ # Strategy 2: import via registered Python
3001
+ if _ext_ver == "unknown":
3002
+ _ver_r2 = _sp.run(
3003
+ [_reg_cmd, "-c",
3004
+ "import sourcecode; print(sourcecode.__version__)"],
3005
+ capture_output=True, text=True, timeout=5,
3006
+ )
3007
+ if _ver_r2.returncode == 0 and _ver_r2.stdout.strip():
3008
+ _ext_ver = _ver_r2.stdout.strip()
3009
+ except Exception:
3010
+ pass
3011
+ if _ext_ver != "unknown" and _ext_ver != _cli_version:
3012
+ typer.echo(
3013
+ f" ⚠ VERSION DRIFT: external server version={_ext_ver}, "
3014
+ f"CLI version={_cli_version}"
3015
+ )
3016
+ typer.echo(
3017
+ " To fix: sourcecode mcp init (re-register using CLI built-in server)"
3018
+ )
3019
+ elif _ext_ver != "unknown":
3020
+ typer.echo(f" External server version={_ext_ver} (matches CLI ✓)")
3021
+ else:
3022
+ typer.echo(
3023
+ f" ⚠ Cannot verify external server version (CLI={_cli_version}). "
3024
+ "Re-run: sourcecode mcp init to switch to built-in server."
3025
+ )
2761
3026
  else:
2762
- typer.echo(f" {client.name:<20} ✗ not configured")
3027
+ typer.echo(f" {client.name:<20} ✗ not configured (app found, but sourcecode entry missing)")
2763
3028
  typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
2764
3029
  typer.echo("")
2765
3030
 
2766
- # Stage 3: Connectivity
2767
- typer.echo("Connectivity")
3031
+ # Stage 3: Process liveness — is the client app currently running?
3032
+ # This is independent from config: a running app may still need restart to pick up config.
3033
+ typer.echo("Runtime (client app process running?)")
2768
3034
  any_installed = any(c.app_installed for c in clients)
2769
3035
  if not any_installed:
2770
- typer.echo(" (no clients to check)")
3036
+ typer.echo(" (no client apps found — nothing to check)")
2771
3037
  else:
2772
3038
  for client in clients:
2773
3039
  if not client.app_installed:
@@ -2779,6 +3045,8 @@ def mcp_status() -> None:
2779
3045
  typer.echo(f" Fix: open {client.name}, then run sourcecode mcp status")
2780
3046
 
2781
3047
  typer.echo(sep)
3048
+ typer.echo(" Note: 'configured' and 'running' are checked independently.")
3049
+ typer.echo(" A running app still needs restart after first-time config.")
2782
3050
  typer.echo(" Setup: sourcecode mcp init")
2783
3051
  typer.echo(" Remove: sourcecode mcp remove")
2784
3052
 
@@ -15,9 +15,15 @@ from typing import Any
15
15
 
16
16
  from mcp.server.fastmcp import FastMCP
17
17
 
18
+ from sourcecode import __version__ as _sourcecode_version
18
19
  from sourcecode.mcp.runner import run_command
19
20
 
21
+ # FIX-P0-5: MCP server version must match CLI version exactly.
22
+ # FastMCP does not accept version= in __init__; inject it on the underlying
23
+ # low-level Server so the MCP initialize handshake reports the correct version.
20
24
  mcp = FastMCP("sourcecode")
25
+ if hasattr(mcp, "_mcp_server"):
26
+ mcp._mcp_server.version = _sourcecode_version # type: ignore[attr-defined]
21
27
 
22
28
 
23
29
  def _ok(data: Any) -> dict:
@@ -37,10 +43,14 @@ def _execute(args: list[str]) -> dict:
37
43
 
38
44
  @mcp.tool()
39
45
  def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict:
40
- """High-signal summary of a repository (~1000-3000 tokens).
46
+ """Compact human/LLM summary of a repository (~1000-3000 tokens). USE THIS FIRST.
47
+
48
+ Best for: quick project orientation, first-time context, token-budget constrained tasks.
49
+ Returns: stacks, entry points, dependency summary, architecture summary, confidence, gaps.
50
+ Includes security_surface, mybatis, and transactional_boundaries for Java/Spring projects.
51
+ For richer machine-oriented detail (deeper signals, more sections), use get_agent_context.
41
52
 
42
53
  Maps to: sourcecode <repo_path> --compact [--git-context]
43
- Returns: stacks, entry points, dependency summary, confidence, gaps.
44
54
  repo_path: absolute path to the repository (default: current working directory).
45
55
  git_context: include git log and branch context in the analysis.
46
56
  """
@@ -56,10 +66,14 @@ def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict
56
66
 
57
67
  @mcp.tool()
58
68
  def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
59
- """Agent-optimised analysis: identity, entry points, dependencies, gaps.
69
+ """Full structured agent context with extended machine-oriented signals (~5000-15000 tokens).
70
+
71
+ Best for: deep analysis, bug investigation, code review, or when get_compact_context
72
+ lacks sufficient detail. Includes all compact fields plus: env_map, code_notes,
73
+ architecture layers, security surface, transactional boundaries, module graph summary.
74
+ Prefer get_compact_context for quick orientation or token-constrained workflows.
60
75
 
61
76
  Maps to: sourcecode <repo_path> --agent [--git-context]
62
- Returns: structured noise-free JSON for AI agents.
63
77
  repo_path: absolute path to the repository (default: current working directory).
64
78
  git_context: include git log and branch context in the analysis.
65
79
  """
@@ -79,8 +93,14 @@ def get_endpoints(repo_path: str = ".") -> dict:
79
93
 
80
94
  Maps to: sourcecode endpoints <repo_path>
81
95
  Returns: endpoints list with method, path, controller, handler fields;
82
- total (int) and undocumented (int) counts.
96
+ security dict when authorization annotations are present
97
+ (policy: roles_allowed|permit_all|deny_all|authenticated|...);
98
+ total (int) and no_security_signal (int) counts.
99
+ no_security_signal counts endpoints with no recognized auth annotation —
100
+ repos using framework-level auth (e.g. Keycloak) may show high counts.
83
101
  Supports Spring MVC (@GetMapping etc.) and JAX-RS (@GET/@POST etc.).
102
+ Security annotations detected: @RolesAllowed, @PermitAll, @DenyAll,
103
+ @Authenticated, @PreAuthorize, @Secured, @SecurityRequirement, @M3FiltroSeguridad.
84
104
  repo_path: absolute path to the repository (default: current working directory).
85
105
  """
86
106
  if not isinstance(repo_path, str):
@@ -121,11 +141,17 @@ def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
121
141
 
122
142
  @mcp.tool()
123
143
  def get_ir_summary(repo_path: str = ".") -> dict:
124
- """Deterministic symbol-level IR summary for Java repositories.
144
+ """Deterministic symbol-level IR summary for Java repositories. Java only.
125
145
 
126
146
  Maps to: sourcecode repo-ir <repo_path> --summary-only
127
- Returns: analysis summary, impact, and change_set omits full graph nodes/edges.
128
- repo_path: absolute path to the repository (default: current working directory).
147
+ Returns: reverse_graph (top 10 hubs), route_surface (top 50 endpoints),
148
+ subsystems (top 15), impact, analysis. Full graph nodes/edges omitted.
149
+
150
+ Output is bounded to ~100 KB for LLM safety. For full IR (can exceed 10 MB
151
+ on large repos), use the CLI: sourcecode repo-ir <path> --output ir.json
152
+ Use get_compact_context or get_agent_context for non-Java repos.
153
+
154
+ repo_path: absolute path to the Java repository (default: current working directory).
129
155
  """
130
156
  if not isinstance(repo_path, str):
131
157
  return _err("repo_path must be a string", "INVALID_ARGUMENT")
@@ -223,6 +249,34 @@ def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> d
223
249
  return _execute(args)
224
250
 
225
251
 
252
+ @mcp.tool()
253
+ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -> dict:
254
+ """Blast-radius analysis: who calls a class and what breaks if it changes? Java only.
255
+
256
+ Maps to: sourcecode impact <target> <repo_path> [--depth <depth>]
257
+ Returns: direct_callers, indirect_callers, endpoints_affected,
258
+ transactional_boundaries_touched, risk_score, risk_level, stats.
259
+
260
+ Use this when:
261
+ - Planning a refactor: understand the full call chain before changing a class
262
+ - PR review: assess blast radius of a changed service or utility class
263
+ - Incident triage: find all paths that reach a faulty component
264
+
265
+ target: class name (simple or FQN) or Java file path. Examples:
266
+ "UserService", "org.example.UserService", "UserService.java"
267
+ repo_path: absolute path to the Java repository (default: current working directory).
268
+ depth: BFS depth for indirect caller traversal (1–8, default: 4).
269
+ """
270
+ if not isinstance(repo_path, str):
271
+ return _err("repo_path must be a string", "INVALID_ARGUMENT")
272
+ if not isinstance(target, str) or not target.strip():
273
+ return _err("target must be a non-empty class name or FQN", "INVALID_ARGUMENT")
274
+ if not isinstance(depth, int) or depth < 1 or depth > 8:
275
+ return _err("depth must be an integer between 1 and 8", "INVALID_ARGUMENT")
276
+ args = ["impact", target.strip(), repo_path, "--depth", str(depth)]
277
+ return _execute(args)
278
+
279
+
226
280
  _TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
227
281
 
228
282
 
@@ -248,12 +302,14 @@ def config() -> dict:
248
302
  def telemetry(action: str) -> dict:
249
303
  """Manage telemetry settings.
250
304
 
251
- Maps to: sourcecode telemetry <status|enable|disable>
252
- action must be one of: status, enable, disable
305
+ Maps to: sourcecode telemetry <action>
306
+ action: one of "status" (show current state), "enable" (opt in), "disable" (opt out).
307
+ Valid values: "status" | "enable" | "disable"
253
308
  """
309
+ # FIX-P2-10: enumerate valid actions in docstring so agents don't guess.
254
310
  if action not in _TELEMETRY_ACTIONS:
255
311
  return _err(
256
- f"action must be one of {sorted(_TELEMETRY_ACTIONS)}",
312
+ f"Invalid action '{action}'. Must be one of: {', '.join(sorted(_TELEMETRY_ACTIONS))}",
257
313
  "INVALID_ARGUMENT",
258
314
  )
259
315
  return _execute(["telemetry", action])