sourcecode 1.31.12__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 (246) hide show
  1. {sourcecode-1.31.12 → sourcecode-1.31.13}/PKG-INFO +3 -3
  2. {sourcecode-1.31.12 → sourcecode-1.31.13}/README.md +2 -2
  3. {sourcecode-1.31.12 → sourcecode-1.31.13}/pyproject.toml +1 -1
  4. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/cli.py +233 -4
  6. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/server.py +44 -4
  7. sourcecode-1.31.13/src/sourcecode/output_budget.py +157 -0
  8. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/repository_ir.py +567 -49
  9. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/serializer.py +41 -4
  10. sourcecode-1.31.13/tests/test_audit_sas_v2.py +506 -0
  11. sourcecode-1.31.13/tests/test_enterprise_benchmarks.py +967 -0
  12. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_repository_ir.py +327 -0
  13. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_v1_10_regressions.py +119 -7
  14. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-0911b79e.json +0 -3825
  15. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-37df4554.json +0 -266
  16. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-624321f3.json +0 -12478
  17. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-776b4676.json +0 -386
  18. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-9770fba7.json +0 -377
  19. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-c4e3c102.json +0 -266
  20. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e8bc5fb4.json +0 -122
  21. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e9801942.json +0 -223
  22. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-ee60e0cd.json +0 -322
  23. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-fdd9d3f7.json +0 -552
  24. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-27b713ac.json +0 -386
  25. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-28e558ac.json +0 -325
  26. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-2d7eca5f.json +0 -12577
  27. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-7cd91058.json +0 -226
  28. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-8ea5b5bb.json +0 -380
  29. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-bef1a526.json +0 -261
  30. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-c69bcdc8.json +0 -122
  31. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-d2dfc690.json +0 -3825
  32. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-dc30daab.json +0 -552
  33. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-20354ec3.json +0 -261
  34. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-2c874f8c.json +0 -122
  35. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-6c8157db.json +0 -226
  36. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-93090ce0.json +0 -129
  37. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ce79b4bf.json +0 -3846
  38. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-cf0bfa7a.json +0 -325
  39. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-e73c6323.json +0 -261
  40. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ee60e0cd.json +0 -325
  41. {sourcecode-1.31.12 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  42. {sourcecode-1.31.12 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  43. {sourcecode-1.31.12 → sourcecode-1.31.13}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  44. {sourcecode-1.31.12 → sourcecode-1.31.13}/.continue-here.md +0 -0
  45. {sourcecode-1.31.12 → sourcecode-1.31.13}/.github/workflows/build-windows.yml +0 -0
  46. {sourcecode-1.31.12 → sourcecode-1.31.13}/.gitignore +0 -0
  47. {sourcecode-1.31.12 → sourcecode-1.31.13}/.ruff.toml +0 -0
  48. {sourcecode-1.31.12 → sourcecode-1.31.13}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
  49. {sourcecode-1.31.12 → sourcecode-1.31.13}/CHANGELOG.md +0 -0
  50. {sourcecode-1.31.12 → sourcecode-1.31.13}/CONTRIBUTING.md +0 -0
  51. {sourcecode-1.31.12 → sourcecode-1.31.13}/LICENSE +0 -0
  52. {sourcecode-1.31.12 → sourcecode-1.31.13}/SECURITY.md +0 -0
  53. {sourcecode-1.31.12 → sourcecode-1.31.13}/docs/privacy.md +0 -0
  54. {sourcecode-1.31.12 → sourcecode-1.31.13}/docs/schema.md +0 -0
  55. {sourcecode-1.31.12 → sourcecode-1.31.13}/raw +0 -0
  56. {sourcecode-1.31.12 → sourcecode-1.31.13}/run_cli.py +0 -0
  57. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/adaptive_scanner.py +0 -0
  58. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/architecture_analyzer.py +0 -0
  59. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/architecture_summary.py +0 -0
  60. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/ast_extractor.py +0 -0
  61. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/classifier.py +0 -0
  62. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/code_notes_analyzer.py +0 -0
  63. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/confidence_analyzer.py +0 -0
  64. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/context_scorer.py +0 -0
  65. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/context_summarizer.py +0 -0
  66. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/contract_model.py +0 -0
  67. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/contract_pipeline.py +0 -0
  68. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/coverage_parser.py +0 -0
  69. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/dependency_analyzer.py +0 -0
  70. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/__init__.py +0 -0
  71. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/base.py +0 -0
  72. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/csproj_parser.py +0 -0
  73. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/dart.py +0 -0
  74. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/dotnet.py +0 -0
  75. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/elixir.py +0 -0
  76. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/go.py +0 -0
  77. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/heuristic.py +0 -0
  78. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/hybrid.py +0 -0
  79. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/java.py +0 -0
  80. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/jvm_ext.py +0 -0
  81. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/nodejs.py +0 -0
  82. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/parsers.py +0 -0
  83. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/php.py +0 -0
  84. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/project.py +0 -0
  85. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/python.py +0 -0
  86. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/ruby.py +0 -0
  87. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/rust.py +0 -0
  88. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/systems.py +0 -0
  89. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/terraform.py +0 -0
  90. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/detectors/tooling.py +0 -0
  91. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/doc_analyzer.py +0 -0
  92. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/entrypoint_classifier.py +0 -0
  93. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/env_analyzer.py +0 -0
  94. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/file_classifier.py +0 -0
  95. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/flow_analyzer.py +0 -0
  96. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/git_analyzer.py +0 -0
  97. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/graph_analyzer.py +0 -0
  98. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/__init__.py +0 -0
  99. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  100. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  101. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  102. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  103. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  104. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/mcp/runner.py +0 -0
  105. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/metrics_analyzer.py +0 -0
  106. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/path_filters.py +0 -0
  107. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/pr_comment_renderer.py +0 -0
  108. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/prepare_context.py +0 -0
  109. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/progress.py +0 -0
  110. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/ranking_engine.py +0 -0
  111. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/redactor.py +0 -0
  112. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/relevance_scorer.py +0 -0
  113. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/repo_classifier.py +0 -0
  114. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/runtime_classifier.py +0 -0
  115. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/scanner.py +0 -0
  116. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/schema.py +0 -0
  117. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/semantic_analyzer.py +0 -0
  118. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/summarizer.py +0 -0
  119. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/__init__.py +0 -0
  120. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/config.py +0 -0
  121. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/consent.py +0 -0
  122. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/events.py +0 -0
  123. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/filters.py +0 -0
  124. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/telemetry/transport.py +0 -0
  125. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/tree_utils.py +0 -0
  126. {sourcecode-1.31.12 → sourcecode-1.31.13}/src/sourcecode/workspace.py +0 -0
  127. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/__init__.py +0 -0
  128. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/conftest.py +0 -0
  129. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/coverage.xml +0 -0
  130. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  131. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/fastapi_app/src/main.py +0 -0
  132. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  133. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/go_service/go.mod +0 -0
  134. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/jacoco.xml +0 -0
  135. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/latin1_sample.java +0 -0
  136. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/latin1_sample_iso.java +0 -0
  137. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/lcov.info +0 -0
  138. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  139. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/package.json +0 -0
  140. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  141. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  142. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  143. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  144. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  145. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  146. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  147. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  148. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  149. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  150. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  151. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  152. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  153. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  154. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  155. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  156. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  157. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  158. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  159. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  160. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  161. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  162. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  163. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  164. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  165. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  166. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  167. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  168. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  169. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  170. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  171. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  172. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  173. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_architecture_analyzer.py +0 -0
  174. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_architecture_summary.py +0 -0
  175. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_ast_extractor.py +0 -0
  176. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_audit_fixes.py +0 -0
  177. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_block1_reliability.py +0 -0
  178. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_block2_coverage.py +0 -0
  179. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_block5_quality.py +0 -0
  180. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_broadleaf_fixes.py +0 -0
  181. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_bug_fixes_v1302.py +0 -0
  182. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_bug_fixes_v1312.py +0 -0
  183. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_bug_fixes_v1313.py +0 -0
  184. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_bug_fixes_v16.py +0 -0
  185. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_bug_fixes_v2.py +0 -0
  186. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_classifier.py +0 -0
  187. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_cli.py +0 -0
  188. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_code_notes_analyzer.py +0 -0
  189. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_context_scorer.py +0 -0
  190. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_contract_pipeline.py +0 -0
  191. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_coverage_parser.py +0 -0
  192. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_cross_consistency.py +0 -0
  193. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_dependency_analyzer_node_python.py +0 -0
  194. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_dependency_analyzer_polyglot.py +0 -0
  195. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_dependency_schema.py +0 -0
  196. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_dotnet.py +0 -0
  197. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_go_rust_java.py +0 -0
  198. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_nodejs.py +0 -0
  199. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_php_ruby_dart.py +0 -0
  200. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_python.py +0 -0
  201. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_universal_managed.py +0 -0
  202. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detector_universal_systems.py +0 -0
  203. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_detectors_base.py +0 -0
  204. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_doc_analyzer_jsdom.py +0 -0
  205. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_doc_analyzer_python.py +0 -0
  206. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_encoding_regression.py +0 -0
  207. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_graph_analyzer_polyglot.py +0 -0
  208. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_graph_analyzer_python_node.py +0 -0
  209. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_graph_schema.py +0 -0
  210. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_hybrid_inference.py +0 -0
  211. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration.py +0 -0
  212. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_dependencies.py +0 -0
  213. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_detection.py +0 -0
  214. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_docs.py +0 -0
  215. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_graph_modules.py +0 -0
  216. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_lqn.py +0 -0
  217. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_metrics.py +0 -0
  218. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_multistack.py +0 -0
  219. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_semantics.py +0 -0
  220. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_integration_universal.py +0 -0
  221. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_java_spring_integration.py +0 -0
  222. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_mcp_runner.py +0 -0
  223. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_mcp_serve.py +0 -0
  224. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_mcp_tools.py +0 -0
  225. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_metrics_analyzer.py +0 -0
  226. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_output_ux.py +0 -0
  227. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_packaging.py +0 -0
  228. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_phase1_improvements.py +0 -0
  229. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_pipeline_integrity.py +0 -0
  230. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_real_projects.py +0 -0
  231. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_redactor.py +0 -0
  232. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_scanner.py +0 -0
  233. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_schema.py +0 -0
  234. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_schema_normalization.py +0 -0
  235. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_scoring_calibration.py +0 -0
  236. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_semantic_analyzer_node.py +0 -0
  237. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_semantic_analyzer_python.py +0 -0
  238. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_semantic_import_resolution.py +0 -0
  239. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_semantic_schema.py +0 -0
  240. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_signal_hierarchy.py +0 -0
  241. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_summarizer.py +0 -0
  242. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_surface_honesty.py +0 -0
  243. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_task_differentiation.py +0 -0
  244. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_telemetry.py +0 -0
  245. {sourcecode-1.31.12 → sourcecode-1.31.13}/tests/test_v131_improvements.py +0 -0
  246. {sourcecode-1.31.12 → 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.12
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.12-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.12
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.12-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.12
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.12"
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.12"
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().
@@ -715,6 +715,35 @@ 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(
@@ -1629,6 +1658,9 @@ def main(
1629
1658
  data = agent_view(sm, full=full)
1630
1659
  if not no_redact:
1631
1660
  data = redact_dict(data)
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")
1632
1664
  # FIX-P0-2: agent mode must honour --format yaml (previously always emitted JSON).
1633
1665
  if format == "yaml":
1634
1666
  from io import StringIO
@@ -1657,6 +1689,9 @@ def main(
1657
1689
  data = compact_view(sm, no_tree=no_tree, full=full)
1658
1690
  if not no_redact:
1659
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")
1660
1695
  if format == "yaml":
1661
1696
  from io import StringIO
1662
1697
  from ruamel.yaml import YAML as _YAML
@@ -2191,7 +2226,10 @@ def prepare_context_cmd(
2191
2226
  _sys.stdout.buffer.write(_err_json.encode("utf-8"))
2192
2227
  _sys.stdout.buffer.write(b"\n")
2193
2228
  _sys.stdout.buffer.flush()
2194
- 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)
2195
2233
  out["review_type"] = "pull_request"
2196
2234
  if output.ci_decision:
2197
2235
  out["ci_decision"] = output.ci_decision
@@ -2273,6 +2311,24 @@ def prepare_context_cmd(
2273
2311
  if llm_prompt:
2274
2312
  out["llm_prompt"] = builder.render_prompt(output)
2275
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
+
2276
2332
  if format == "github-comment" and task == "review-pr":
2277
2333
  from sourcecode.pr_comment_renderer import render_github_comment
2278
2334
  _pc_content = render_github_comment(out)
@@ -2490,6 +2546,115 @@ def repo_ir_cmd(
2490
2546
  _sys.stdout.write("\n")
2491
2547
 
2492
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
+
2493
2658
  # ── endpoints ─────────────────────────────────────────────────────────────────
2494
2659
 
2495
2660
  # _extract_java_endpoints is imported from sourcecode.repository_ir as the
@@ -2756,6 +2921,9 @@ def mcp_init(
2756
2921
  @mcp_app.command("status")
2757
2922
  def mcp_status() -> None:
2758
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
2759
2927
  from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
2760
2928
  from sourcecode.mcp.onboarding import applier
2761
2929
 
@@ -2764,6 +2932,10 @@ def mcp_status() -> None:
2764
2932
  typer.echo("MCP Status")
2765
2933
  typer.echo(sep)
2766
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
+
2767
2939
  # Stage 1: Dependencies
2768
2940
  try:
2769
2941
  import mcp as _mcp_pkg # noqa: F401
@@ -2782,8 +2954,7 @@ def mcp_status() -> None:
2782
2954
 
2783
2955
  # Stage 2: Config files — is sourcecode registered in the client's config?
2784
2956
  # FIX-P0-6: "configured" and "running" are distinct, independent checks.
2785
- # A client app may be running without sourcecode configured, or configured but not running.
2786
- # Display each state as a separate labelled field to prevent contradiction.
2957
+ # Also detect external server installs (different Python/executable than CLI).
2787
2958
  typer.echo("Config (sourcecode registered in client config?)")
2788
2959
  for client in clients:
2789
2960
  if not client.app_installed:
@@ -2794,6 +2965,64 @@ def mcp_status() -> None:
2794
2965
  config = applier.read_config(client.config_path)
2795
2966
  if applier.is_installed(config):
2796
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
+ )
2797
3026
  else:
2798
3027
  typer.echo(f" {client.name:<20} ✗ not configured (app found, but sourcecode entry missing)")
2799
3028
  typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
@@ -93,8 +93,14 @@ def get_endpoints(repo_path: str = ".") -> dict:
93
93
 
94
94
  Maps to: sourcecode endpoints <repo_path>
95
95
  Returns: endpoints list with method, path, controller, handler fields;
96
- 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.
97
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.
98
104
  repo_path: absolute path to the repository (default: current working directory).
99
105
  """
100
106
  if not isinstance(repo_path, str):
@@ -135,11 +141,17 @@ def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
135
141
 
136
142
  @mcp.tool()
137
143
  def get_ir_summary(repo_path: str = ".") -> dict:
138
- """Deterministic symbol-level IR summary for Java repositories.
144
+ """Deterministic symbol-level IR summary for Java repositories. Java only.
139
145
 
140
146
  Maps to: sourcecode repo-ir <repo_path> --summary-only
141
- Returns: analysis summary, impact, and change_set omits full graph nodes/edges.
142
- 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).
143
155
  """
144
156
  if not isinstance(repo_path, str):
145
157
  return _err("repo_path must be a string", "INVALID_ARGUMENT")
@@ -237,6 +249,34 @@ def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> d
237
249
  return _execute(args)
238
250
 
239
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
+
240
280
  _TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
241
281
 
242
282
 
@@ -0,0 +1,157 @@
1
+ """output_budget.py — Progressive output trimming for LLM context safety.
2
+
3
+ Single entry point: trim_to_budget(data, budget_bytes, label="").
4
+
5
+ Protects against runaway output on large repos by progressively dropping
6
+ or truncating lower-priority sections while preserving the highest-value signals.
7
+
8
+ Trim priority (lowest → highest value, trimmed first):
9
+ 1. Inner boost/match lists inside symptom_explain, transactional_boundaries, mybatis
10
+ 2. Medium-value lists: code_notes, env_map, related_notes
11
+ 3. High-cost sections: relevant_files, suspected_areas, key_dependencies
12
+ 4. Last resort: drop non-essential sections entirely
13
+
14
+ Always preserved: project_type, project_summary, architecture_summary, task,
15
+ goal, confidence, analysis_gaps, error, ci_decision, summary, warnings, hint.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from typing import Any
22
+
23
+
24
+ # Sections that must never be removed (structural identity + user-facing signals)
25
+ _ALWAYS_KEEP: frozenset[str] = frozenset({
26
+ "task", "goal",
27
+ "project_type", "project_summary", "architecture_summary",
28
+ "schema_version",
29
+ "confidence", "confidence_summary", "analysis_gaps", "gaps",
30
+ "error", "error_code", "message",
31
+ "ci_decision", "summary",
32
+ "warnings", "hint", "_budget_note",
33
+ })
34
+
35
+
36
+ # Each step: (top_key, inner_key_or_None, max_items)
37
+ # inner_key=None → trim/drop top_key itself
38
+ # max_items=0 → drop the section entirely
39
+ _TRIM_SCHEDULE: list[tuple[str, str | None, int]] = [
40
+ # Step 1 — trim inner lists in structured sections
41
+ ("symptom_explain", "boosts", 10),
42
+ ("symptom_explain", "content_matches", 5),
43
+ ("symptom_explain", "commit_matches", 5),
44
+ ("symptom_explain", "synonym_matches", 3),
45
+ ("transactional_boundaries", "classes", 5),
46
+ ("mybatis", "dto_mappers", 5),
47
+ # Step 2 — trim medium-value top-level lists
48
+ ("code_notes", None, 5),
49
+ ("env_map", None, 5),
50
+ ("related_notes", None, 5),
51
+ # Step 3 — trim higher-cost sections
52
+ ("relevant_files", None, 15),
53
+ ("suspected_areas", None, 10),
54
+ ("key_dependencies", None, 10),
55
+ # Step 4 — more aggressive
56
+ ("relevant_files", None, 8),
57
+ ("key_dependencies", None, 5),
58
+ ("suspected_areas", None, 5),
59
+ ("symptom_explain", "boosts", 0), # drop inner list
60
+ # Step 5 — drop non-essential sections
61
+ ("symptom_explain", None, 0),
62
+ ("code_notes", None, 0),
63
+ ("env_map", None, 0),
64
+ ("related_notes", None, 0),
65
+ ("angular_analysis", None, 0),
66
+ ("spring_profiles", None, 0),
67
+ ("execution_paths", None, 0),
68
+ ("dependency_graph_summary", None, 0),
69
+ # Step 6 — last resort
70
+ ("relevant_files", None, 3),
71
+ ("suspected_areas", None, 0),
72
+ ("key_dependencies", None, 0),
73
+ ]
74
+
75
+
76
+ def _serialized_size(data: Any) -> int:
77
+ """Byte size of JSON-serialized data (UTF-8)."""
78
+ return len(json.dumps(data, ensure_ascii=False).encode("utf-8"))
79
+
80
+
81
+ def trim_to_budget(data: dict, budget_bytes: int, *, label: str = "") -> dict:
82
+ """Progressively trim *data* to fit within *budget_bytes*.
83
+
84
+ Preserves the highest-value sections. Adds ``_budget_note`` when trimming
85
+ occurs so callers can surface it to users.
86
+
87
+ Returns data unchanged if already within budget.
88
+ """
89
+ if _serialized_size(data) <= budget_bytes:
90
+ return data
91
+
92
+ result: dict = dict(data)
93
+ original_size = _serialized_size(result)
94
+ trimmed_sections: list[str] = []
95
+
96
+ for top_key, inner_key, max_items in _TRIM_SCHEDULE:
97
+ if _serialized_size(result) <= budget_bytes:
98
+ break
99
+
100
+ if top_key not in result or result[top_key] is None:
101
+ continue
102
+
103
+ section_val = result[top_key]
104
+
105
+ if inner_key is None:
106
+ # Trim or drop the top-level key
107
+ if max_items == 0:
108
+ if top_key in _ALWAYS_KEEP:
109
+ continue
110
+ del result[top_key]
111
+ trimmed_sections.append(f"{top_key}:dropped")
112
+ elif isinstance(section_val, list) and len(section_val) > max_items:
113
+ result[top_key] = section_val[:max_items]
114
+ trimmed_sections.append(f"{top_key}≤{max_items}")
115
+ else:
116
+ # Trim or drop an inner list
117
+ if not isinstance(section_val, dict):
118
+ continue
119
+ if inner_key not in section_val:
120
+ continue
121
+ inner_val = section_val[inner_key]
122
+ if max_items == 0:
123
+ new_sec = dict(section_val)
124
+ del new_sec[inner_key]
125
+ result[top_key] = new_sec
126
+ trimmed_sections.append(f"{top_key}.{inner_key}:dropped")
127
+ elif isinstance(inner_val, list) and len(inner_val) > max_items:
128
+ new_sec = dict(section_val)
129
+ new_sec[inner_key] = inner_val[:max_items]
130
+ result[top_key] = new_sec
131
+ trimmed_sections.append(f"{top_key}.{inner_key}≤{max_items}")
132
+
133
+ final_size = _serialized_size(result)
134
+ if trimmed_sections:
135
+ note = (
136
+ f"Output trimmed {original_size // 1024}KB → {final_size // 1024}KB "
137
+ f"(budget {budget_bytes // 1024}KB). "
138
+ f"Trimmed: {', '.join(trimmed_sections)}. "
139
+ "Use --output <file> to capture full output."
140
+ )
141
+ if label:
142
+ note = f"[{label}] {note}"
143
+ result["_budget_note"] = note
144
+
145
+ return result
146
+
147
+
148
+ # Budget constants (bytes) — used by CLI callers
149
+ BUDGET_COMPACT = 30_000 # compact/agent main cmd
150
+ BUDGET_AGENT = 40_000 # agent main cmd (slightly more headroom)
151
+ BUDGET_FIX_BUG = 100_000 # fix-bug (with or without --symptom)
152
+ BUDGET_REVIEW_PR = 100_000 # review-pr
153
+ BUDGET_ONBOARD = 30_000 # onboard
154
+ BUDGET_EXPLAIN = 30_000 # explain
155
+ BUDGET_REFACTOR = 50_000 # refactor
156
+ BUDGET_DELTA = 80_000 # delta (change impact context)
157
+ BUDGET_IMPACT = 50_000 # impact blast-radius command