sourcecode 1.31.12__tar.gz → 1.31.14__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 (248) hide show
  1. {sourcecode-1.31.12 → sourcecode-1.31.14}/PKG-INFO +3 -3
  2. {sourcecode-1.31.12 → sourcecode-1.31.14}/README.md +2 -2
  3. {sourcecode-1.31.12 → sourcecode-1.31.14}/pyproject.toml +1 -1
  4. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/__init__.py +1 -1
  5. sourcecode-1.31.14/src/sourcecode/canonical_ir.py +583 -0
  6. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/cli.py +233 -4
  7. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/server.py +44 -4
  8. sourcecode-1.31.14/src/sourcecode/output_budget.py +157 -0
  9. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/repository_ir.py +516 -58
  10. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/serializer.py +41 -4
  11. sourcecode-1.31.14/tests/test_audit_sas_v2.py +506 -0
  12. sourcecode-1.31.14/tests/test_canonical_ir.py +893 -0
  13. sourcecode-1.31.14/tests/test_enterprise_benchmarks.py +967 -0
  14. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_repository_ir.py +327 -0
  15. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_v1_10_regressions.py +119 -7
  16. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-0911b79e.json +0 -3825
  17. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-37df4554.json +0 -266
  18. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-624321f3.json +0 -12478
  19. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-776b4676.json +0 -386
  20. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-9770fba7.json +0 -377
  21. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-c4e3c102.json +0 -266
  22. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e8bc5fb4.json +0 -122
  23. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e9801942.json +0 -223
  24. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-ee60e0cd.json +0 -322
  25. sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-fdd9d3f7.json +0 -552
  26. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-27b713ac.json +0 -386
  27. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-28e558ac.json +0 -325
  28. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-2d7eca5f.json +0 -12577
  29. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-7cd91058.json +0 -226
  30. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-8ea5b5bb.json +0 -380
  31. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-bef1a526.json +0 -261
  32. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-c69bcdc8.json +0 -122
  33. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-d2dfc690.json +0 -3825
  34. sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-dc30daab.json +0 -552
  35. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-20354ec3.json +0 -261
  36. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-2c874f8c.json +0 -122
  37. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-6c8157db.json +0 -226
  38. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-93090ce0.json +0 -129
  39. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ce79b4bf.json +0 -3846
  40. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-cf0bfa7a.json +0 -325
  41. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-e73c6323.json +0 -261
  42. sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ee60e0cd.json +0 -325
  43. {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  44. {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  45. {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  46. {sourcecode-1.31.12 → sourcecode-1.31.14}/.continue-here.md +0 -0
  47. {sourcecode-1.31.12 → sourcecode-1.31.14}/.github/workflows/build-windows.yml +0 -0
  48. {sourcecode-1.31.12 → sourcecode-1.31.14}/.gitignore +0 -0
  49. {sourcecode-1.31.12 → sourcecode-1.31.14}/.ruff.toml +0 -0
  50. {sourcecode-1.31.12 → sourcecode-1.31.14}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
  51. {sourcecode-1.31.12 → sourcecode-1.31.14}/CHANGELOG.md +0 -0
  52. {sourcecode-1.31.12 → sourcecode-1.31.14}/CONTRIBUTING.md +0 -0
  53. {sourcecode-1.31.12 → sourcecode-1.31.14}/LICENSE +0 -0
  54. {sourcecode-1.31.12 → sourcecode-1.31.14}/SECURITY.md +0 -0
  55. {sourcecode-1.31.12 → sourcecode-1.31.14}/docs/privacy.md +0 -0
  56. {sourcecode-1.31.12 → sourcecode-1.31.14}/docs/schema.md +0 -0
  57. {sourcecode-1.31.12 → sourcecode-1.31.14}/raw +0 -0
  58. {sourcecode-1.31.12 → sourcecode-1.31.14}/run_cli.py +0 -0
  59. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/adaptive_scanner.py +0 -0
  60. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/architecture_analyzer.py +0 -0
  61. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/architecture_summary.py +0 -0
  62. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/ast_extractor.py +0 -0
  63. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/classifier.py +0 -0
  64. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/code_notes_analyzer.py +0 -0
  65. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/confidence_analyzer.py +0 -0
  66. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/context_scorer.py +0 -0
  67. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/context_summarizer.py +0 -0
  68. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/contract_model.py +0 -0
  69. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/contract_pipeline.py +0 -0
  70. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/coverage_parser.py +0 -0
  71. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/dependency_analyzer.py +0 -0
  72. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/__init__.py +0 -0
  73. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/base.py +0 -0
  74. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/csproj_parser.py +0 -0
  75. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/dart.py +0 -0
  76. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/dotnet.py +0 -0
  77. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/elixir.py +0 -0
  78. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/go.py +0 -0
  79. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/heuristic.py +0 -0
  80. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/hybrid.py +0 -0
  81. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/java.py +0 -0
  82. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/jvm_ext.py +0 -0
  83. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/nodejs.py +0 -0
  84. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/parsers.py +0 -0
  85. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/php.py +0 -0
  86. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/project.py +0 -0
  87. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/python.py +0 -0
  88. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/ruby.py +0 -0
  89. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/rust.py +0 -0
  90. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/systems.py +0 -0
  91. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/terraform.py +0 -0
  92. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/tooling.py +0 -0
  93. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/doc_analyzer.py +0 -0
  94. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/entrypoint_classifier.py +0 -0
  95. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/env_analyzer.py +0 -0
  96. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/file_classifier.py +0 -0
  97. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/flow_analyzer.py +0 -0
  98. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/git_analyzer.py +0 -0
  99. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/graph_analyzer.py +0 -0
  100. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/__init__.py +0 -0
  101. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  102. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  103. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  104. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  105. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  106. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/runner.py +0 -0
  107. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/metrics_analyzer.py +0 -0
  108. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/path_filters.py +0 -0
  109. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/pr_comment_renderer.py +0 -0
  110. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/prepare_context.py +0 -0
  111. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/progress.py +0 -0
  112. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/ranking_engine.py +0 -0
  113. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/redactor.py +0 -0
  114. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/relevance_scorer.py +0 -0
  115. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/repo_classifier.py +0 -0
  116. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/runtime_classifier.py +0 -0
  117. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/scanner.py +0 -0
  118. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/schema.py +0 -0
  119. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/semantic_analyzer.py +0 -0
  120. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/summarizer.py +0 -0
  121. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/__init__.py +0 -0
  122. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/config.py +0 -0
  123. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/consent.py +0 -0
  124. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/events.py +0 -0
  125. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/filters.py +0 -0
  126. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/transport.py +0 -0
  127. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/tree_utils.py +0 -0
  128. {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/workspace.py +0 -0
  129. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/__init__.py +0 -0
  130. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/conftest.py +0 -0
  131. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/coverage.xml +0 -0
  132. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  133. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/fastapi_app/src/main.py +0 -0
  134. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  135. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/go_service/go.mod +0 -0
  136. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/jacoco.xml +0 -0
  137. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/latin1_sample.java +0 -0
  138. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/latin1_sample_iso.java +0 -0
  139. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/lcov.info +0 -0
  140. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  141. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/package.json +0 -0
  142. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  143. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  144. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  145. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  146. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  147. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  148. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  149. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  150. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  151. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  152. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  153. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  154. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  155. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  156. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  157. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  158. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  159. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  160. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  161. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  162. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  163. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  164. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  165. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  166. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  167. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  168. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  169. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  170. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  171. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  172. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  173. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  174. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  175. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_architecture_analyzer.py +0 -0
  176. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_architecture_summary.py +0 -0
  177. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_ast_extractor.py +0 -0
  178. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_audit_fixes.py +0 -0
  179. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block1_reliability.py +0 -0
  180. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block2_coverage.py +0 -0
  181. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block5_quality.py +0 -0
  182. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_broadleaf_fixes.py +0 -0
  183. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1302.py +0 -0
  184. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1312.py +0 -0
  185. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1313.py +0 -0
  186. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v16.py +0 -0
  187. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v2.py +0 -0
  188. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_classifier.py +0 -0
  189. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_cli.py +0 -0
  190. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_code_notes_analyzer.py +0 -0
  191. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_context_scorer.py +0 -0
  192. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_contract_pipeline.py +0 -0
  193. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_coverage_parser.py +0 -0
  194. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_cross_consistency.py +0 -0
  195. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_analyzer_node_python.py +0 -0
  196. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_analyzer_polyglot.py +0 -0
  197. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_schema.py +0 -0
  198. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_dotnet.py +0 -0
  199. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_go_rust_java.py +0 -0
  200. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_nodejs.py +0 -0
  201. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_php_ruby_dart.py +0 -0
  202. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_python.py +0 -0
  203. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_universal_managed.py +0 -0
  204. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_universal_systems.py +0 -0
  205. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detectors_base.py +0 -0
  206. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_doc_analyzer_jsdom.py +0 -0
  207. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_doc_analyzer_python.py +0 -0
  208. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_encoding_regression.py +0 -0
  209. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_analyzer_polyglot.py +0 -0
  210. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_analyzer_python_node.py +0 -0
  211. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_schema.py +0 -0
  212. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_hybrid_inference.py +0 -0
  213. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration.py +0 -0
  214. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_dependencies.py +0 -0
  215. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_detection.py +0 -0
  216. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_docs.py +0 -0
  217. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_graph_modules.py +0 -0
  218. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_lqn.py +0 -0
  219. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_metrics.py +0 -0
  220. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_multistack.py +0 -0
  221. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_semantics.py +0 -0
  222. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_universal.py +0 -0
  223. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_java_spring_integration.py +0 -0
  224. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_runner.py +0 -0
  225. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_serve.py +0 -0
  226. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_tools.py +0 -0
  227. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_metrics_analyzer.py +0 -0
  228. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_output_ux.py +0 -0
  229. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_packaging.py +0 -0
  230. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_phase1_improvements.py +0 -0
  231. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_pipeline_integrity.py +0 -0
  232. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_real_projects.py +0 -0
  233. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_redactor.py +0 -0
  234. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_scanner.py +0 -0
  235. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_schema.py +0 -0
  236. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_schema_normalization.py +0 -0
  237. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_scoring_calibration.py +0 -0
  238. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_analyzer_node.py +0 -0
  239. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_analyzer_python.py +0 -0
  240. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_import_resolution.py +0 -0
  241. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_schema.py +0 -0
  242. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_signal_hierarchy.py +0 -0
  243. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_summarizer.py +0 -0
  244. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_surface_honesty.py +0 -0
  245. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_task_differentiation.py +0 -0
  246. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_telemetry.py +0 -0
  247. {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_v131_improvements.py +0 -0
  248. {sourcecode-1.31.12 → sourcecode-1.31.14}/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.14
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.14-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.14
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.14-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.14
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.14"
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.14"
@@ -0,0 +1,583 @@
1
+ """canonical_ir.py — Canonical Repository IR contract (single source of truth).
2
+
3
+ Architecture:
4
+ build_canonical_ir() → CanonicalRepositoryIR
5
+ project_route_surface() → derives route_surface list from CIR
6
+ project_endpoint_surface() → derives endpoint surface dict from CIR
7
+ project_blast_radius() → derives blast-radius dict from CIR
8
+ validate_canonical_ir() → invariant checker; returns violation list
9
+
10
+ All external projections derive exclusively from CanonicalRepositoryIR.
11
+ No view reconstructs endpoint, security, or blast-radius data independently.
12
+
13
+ IR schema version: 1.0.0
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ from dataclasses import dataclass, field
20
+ from pathlib import Path
21
+ from typing import Any, Optional
22
+
23
+ from sourcecode.repository_ir import (
24
+ build_repo_ir,
25
+ compute_blast_radius as _compute_blast_radius,
26
+ )
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Schema version — single constant, embedded in every CIR
30
+ # ---------------------------------------------------------------------------
31
+
32
+ IR_SCHEMA_VERSION = "1.0.0"
33
+
34
+ # Edge types excluded from reverse graph (mirrors repository_ir._REVERSE_EXCLUDE)
35
+ _REVERSE_EXCLUDE: frozenset[str] = frozenset({"annotated_with", "mapped_to"})
36
+
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # CanonicalSecurity
40
+ # ---------------------------------------------------------------------------
41
+
42
+ @dataclass
43
+ class CanonicalSecurity:
44
+ """Canonical security policy for an endpoint.
45
+
46
+ Single source of truth for authorization metadata.
47
+ Never reconstructed from annotations in independent code paths.
48
+
49
+ source_scope: where the annotation lives —
50
+ "method" annotation on the handler method
51
+ "class" annotation on the controller class (method has none)
52
+ "inherited" endpoint inherited from parent class
53
+ """
54
+ policy: str # deny_all|permit_all|roles_allowed|authenticated|...
55
+ source_scope: str # method|class|inherited
56
+ effective_roles: list[str] = field(default_factory=list)
57
+ expression: str = "" # SpEL for @PreAuthorize/@PostAuthorize
58
+ required_permission: str = "" # for @M3FiltroSeguridad
59
+ raw: dict = field(default_factory=dict) # full original policy dict
60
+
61
+ def to_dict(self) -> dict:
62
+ """Serialize for external consumption (omits internal fields)."""
63
+ out: dict = {"policy": self.policy}
64
+ if self.effective_roles:
65
+ out["roles"] = self.effective_roles
66
+ if self.expression:
67
+ out["expression"] = self.expression
68
+ if self.required_permission:
69
+ out["required_permission"] = self.required_permission
70
+ return out
71
+
72
+ def to_full_dict(self) -> dict:
73
+ """Full serialization including source_scope (for CIR audit/debug)."""
74
+ out = self.to_dict()
75
+ out["source_scope"] = self.source_scope
76
+ return out
77
+
78
+ @classmethod
79
+ def from_policy_dict(
80
+ cls, d: dict, *, source_scope: str = "method"
81
+ ) -> "CanonicalSecurity":
82
+ """Build from the policy dict emitted by _route_security_from_sym."""
83
+ return cls(
84
+ policy=d.get("policy", ""),
85
+ source_scope=source_scope,
86
+ effective_roles=list(d.get("roles", [])),
87
+ expression=d.get("expression", ""),
88
+ required_permission=d.get("required_permission", ""),
89
+ raw=dict(d),
90
+ )
91
+
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # CanonicalEndpoint
95
+ # ---------------------------------------------------------------------------
96
+
97
+ @dataclass
98
+ class CanonicalEndpoint:
99
+ """Canonical endpoint entity — single source of truth for REST endpoint data.
100
+
101
+ id is deterministic: METHOD:path:controller_class:handler_symbol
102
+ Field names are stable and typed — never loose dicts with optional fields.
103
+
104
+ No independent reconstruction: always derived from route_surface in IR.
105
+ """
106
+ id: str # METHOD:path:controller_fqn:handler_symbol
107
+ path: str
108
+ method: str
109
+ controller_class: str # FQN of controller class
110
+ handler_symbol: str # FQN of handler method: pkg.Class#method
111
+ security: Optional[CanonicalSecurity]
112
+ source_file: str
113
+ stable_id: str
114
+ inheritance_depth: int = 0
115
+ dependent_symbols: list[str] = field(default_factory=list) # lazy, filled by blast-radius
116
+
117
+ def to_dict(self) -> dict:
118
+ out: dict = {
119
+ "id": self.id,
120
+ "path": self.path,
121
+ "method": self.method,
122
+ "controller_class": self.controller_class,
123
+ "handler_symbol": self.handler_symbol,
124
+ "source_file": self.source_file,
125
+ "stable_id": self.stable_id,
126
+ "inheritance_depth": self.inheritance_depth,
127
+ }
128
+ if self.security is not None:
129
+ out["security"] = self.security.to_dict()
130
+ if self.dependent_symbols:
131
+ out["dependent_symbols"] = self.dependent_symbols
132
+ return out
133
+
134
+ @staticmethod
135
+ def make_id(
136
+ method: str, path: str, controller_class: str, handler_symbol: str
137
+ ) -> str:
138
+ """Deterministic endpoint ID — stable across formatting/body changes."""
139
+ return f"{method}:{path}:{controller_class}:{handler_symbol}"
140
+
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # CanonicalRepositoryIR
144
+ # ---------------------------------------------------------------------------
145
+
146
+ @dataclass
147
+ class CanonicalRepositoryIR:
148
+ """Canonical Repository IR — single source of truth for all code intelligence.
149
+
150
+ Projections that MUST derive from this structure:
151
+ project_route_surface() route_surface list
152
+ project_endpoint_surface() endpoint surface dict (replaces extract_java_endpoints)
153
+ project_blast_radius() blast-radius dict
154
+
155
+ No view is allowed to independently reconstruct endpoints, security, or
156
+ blast-radius data — all must project from cir.endpoints / cir.reverse_graph.
157
+ """
158
+ schema_version: str # always IR_SCHEMA_VERSION
159
+ cir_hash: str # sha256 fingerprint
160
+ files: list[str] # sorted relative file paths
161
+ symbols: list[str] # sorted symbol FQNs
162
+ call_graph: list[dict] # sorted forward edges
163
+ reverse_graph: dict[str, dict[str, list[str]]] # target → {type → [callers]}
164
+ dependencies: list[dict] # import/extends/implements edges
165
+ endpoints: list[CanonicalEndpoint] # canonical endpoint list
166
+ security_index: dict[str, CanonicalSecurity] # handler_symbol → security
167
+ metadata: dict[str, Any] # stats, gaps, subsystems, etc.
168
+ # Raw IR dict retained for projections that need full IR fields
169
+ # (e.g. project_blast_radius delegates to compute_blast_radius)
170
+ _raw_ir: dict = field(default_factory=dict, repr=False, compare=False)
171
+
172
+
173
+ # ---------------------------------------------------------------------------
174
+ # Fingerprint
175
+ # ---------------------------------------------------------------------------
176
+
177
+ def _compute_cir_hash(
178
+ schema_version: str,
179
+ files: list[str],
180
+ symbols: list[str],
181
+ endpoints: list[CanonicalEndpoint],
182
+ call_graph: list[dict],
183
+ ) -> str:
184
+ """Compute deterministic sha256 fingerprint of canonical IR content.
185
+
186
+ Identical repo + schema_version → identical hash.
187
+ Changes on: any symbol/endpoint added or removed, schema version bump.
188
+ Stable across: formatting, comments, ordering differences.
189
+ """
190
+ edge_keys = sorted(
191
+ f"{e.get('from', '')}:{e.get('type', '')}:{e.get('to', '')}"
192
+ for e in call_graph
193
+ )
194
+ endpoint_keys = sorted(ep.id for ep in endpoints)
195
+
196
+ content = {
197
+ "schema_version": schema_version,
198
+ "files": sorted(files),
199
+ "symbols": sorted(symbols),
200
+ "edges": edge_keys,
201
+ "endpoints": endpoint_keys,
202
+ }
203
+ raw = json.dumps(content, sort_keys=True, ensure_ascii=False)
204
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Conversion helpers
209
+ # ---------------------------------------------------------------------------
210
+
211
+ def _route_to_canonical_endpoint(route: dict) -> CanonicalEndpoint:
212
+ """Convert a route_surface entry to a CanonicalEndpoint.
213
+
214
+ Canonical field mapping:
215
+ route["effective_class"] | route["controller"] → controller_class (FQN)
216
+ route["symbol"] → handler_symbol (FQN)
217
+ route["security_annotations"] → security (CanonicalSecurity)
218
+
219
+ This is the ONLY place that reads route_surface dict fields.
220
+ All other code must read CanonicalEndpoint attributes.
221
+ """
222
+ controller_class = (
223
+ route.get("effective_class")
224
+ or route.get("controller")
225
+ or route.get("declaring_class")
226
+ or ""
227
+ )
228
+ handler_symbol = route.get("symbol") or ""
229
+ method = route.get("method") or ""
230
+ path = route.get("path") or ""
231
+
232
+ security_dict = route.get("security_annotations")
233
+ security: Optional[CanonicalSecurity] = None
234
+ if security_dict:
235
+ # Determine source_scope from inheritance_depth:
236
+ # depth=0 → annotation on method or class (method takes precedence)
237
+ # depth>0 → inherited from parent class
238
+ depth = route.get("inheritance_depth") or 0
239
+ scope = "inherited" if depth > 0 else "method"
240
+ security = CanonicalSecurity.from_policy_dict(security_dict, source_scope=scope)
241
+
242
+ endpoint_id = CanonicalEndpoint.make_id(method, path, controller_class, handler_symbol)
243
+
244
+ return CanonicalEndpoint(
245
+ id=endpoint_id,
246
+ path=path,
247
+ method=method,
248
+ controller_class=controller_class,
249
+ handler_symbol=handler_symbol,
250
+ security=security,
251
+ source_file=route.get("source_file") or "",
252
+ stable_id=route.get("stable_id") or "",
253
+ inheritance_depth=route.get("inheritance_depth") or 0,
254
+ )
255
+
256
+
257
+ def ir_dict_to_canonical(
258
+ ir: dict,
259
+ *,
260
+ file_paths: Optional[list[str]] = None,
261
+ ) -> CanonicalRepositoryIR:
262
+ """Convert build_repo_ir output dict to CanonicalRepositoryIR.
263
+
264
+ Single entry point for CIR construction from an already-built IR dict.
265
+ All projections should use this rather than working with the raw IR dict.
266
+
267
+ Args:
268
+ ir: Full IR dict from build_repo_ir.
269
+ file_paths: Optional explicit file list (used for files field).
270
+ If None, derived from graph node source_file fields.
271
+ """
272
+ graph = ir.get("graph") or {}
273
+ nodes = graph.get("nodes") or []
274
+ edges = graph.get("edges") or []
275
+
276
+ # Sorted symbol FQNs — stable ordering
277
+ symbols = sorted(n["fqn"] for n in nodes if "fqn" in n)
278
+
279
+ # Sorted file paths — stable ordering
280
+ if file_paths is not None:
281
+ files = sorted(file_paths)
282
+ else:
283
+ file_set: set[str] = set()
284
+ for n in nodes:
285
+ sf = n.get("source_file") or ""
286
+ if sf:
287
+ file_set.add(sf)
288
+ files = sorted(file_set)
289
+
290
+ # Sorted call graph edges — stable ordering: from, type, to
291
+ call_graph = sorted(
292
+ edges,
293
+ key=lambda e: (e.get("from", ""), e.get("type", ""), e.get("to", "")),
294
+ )
295
+
296
+ # Dependency edges (structural subset of call_graph)
297
+ _dep_types = frozenset({"imports", "extends", "implements", "injects"})
298
+ dependencies = [e for e in call_graph if e.get("type") in _dep_types]
299
+
300
+ # Build canonical endpoints from route_surface — stable ordering
301
+ route_surface = ir.get("route_surface") or []
302
+ # Deduplicate by endpoint id (route_surface can have duplicates from multi-prefix)
303
+ _seen_ids: set[str] = set()
304
+ raw_endpoints: list[CanonicalEndpoint] = []
305
+ for r in route_surface:
306
+ ep = _route_to_canonical_endpoint(r)
307
+ if ep.id not in _seen_ids:
308
+ _seen_ids.add(ep.id)
309
+ raw_endpoints.append(ep)
310
+
311
+ endpoints = sorted(
312
+ raw_endpoints,
313
+ key=lambda ep: (ep.method, ep.path, ep.controller_class, ep.handler_symbol),
314
+ )
315
+
316
+ # Security index: handler_symbol → CanonicalSecurity
317
+ # For handlers with security, record from the most-specific source
318
+ security_index: dict[str, CanonicalSecurity] = {}
319
+ for ep in endpoints:
320
+ if ep.security is not None and ep.handler_symbol:
321
+ security_index[ep.handler_symbol] = ep.security
322
+
323
+ # Metadata aggregation
324
+ metadata: dict[str, Any] = {
325
+ "schema_version": IR_SCHEMA_VERSION,
326
+ "symbol_count": len(symbols),
327
+ "endpoint_count": len(endpoints),
328
+ "file_count": len(files),
329
+ "edge_count": len(call_graph),
330
+ "subsystems": ir.get("subsystems") or [],
331
+ "analysis_gaps": ir.get("analysis_gaps") or [],
332
+ "spring_events": ir.get("spring_events") or {},
333
+ "score_basis": (ir.get("impact") or {}).get("score_basis", "none"),
334
+ "reverse_graph_size": len(ir.get("reverse_graph") or {}),
335
+ }
336
+
337
+ cir_hash = _compute_cir_hash(
338
+ IR_SCHEMA_VERSION, files, symbols, endpoints, call_graph
339
+ )
340
+
341
+ return CanonicalRepositoryIR(
342
+ schema_version=IR_SCHEMA_VERSION,
343
+ cir_hash=cir_hash,
344
+ files=files,
345
+ symbols=symbols,
346
+ call_graph=call_graph,
347
+ reverse_graph=ir.get("reverse_graph") or {},
348
+ dependencies=dependencies,
349
+ endpoints=endpoints,
350
+ security_index=security_index,
351
+ metadata=metadata,
352
+ _raw_ir=ir,
353
+ )
354
+
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # Public builder
358
+ # ---------------------------------------------------------------------------
359
+
360
+ def build_canonical_ir(
361
+ file_paths: list[str],
362
+ root: Path,
363
+ *,
364
+ since: Optional[str] = None,
365
+ ) -> CanonicalRepositoryIR:
366
+ """Build CanonicalRepositoryIR from Java files.
367
+
368
+ Single source of truth builder — the canonical entry point for code intelligence.
369
+ Delegates Java IR extraction to build_repo_ir (5-phase pipeline unchanged).
370
+ Returns a CanonicalRepositoryIR from which all projections must derive.
371
+
372
+ Args:
373
+ file_paths: Relative paths to Java files (from find_java_files).
374
+ root: Absolute repo root.
375
+ since: Git ref for symbol diff (e.g. "HEAD~1", "main").
376
+ """
377
+ ir = build_repo_ir(file_paths, root, since=since)
378
+ return ir_dict_to_canonical(ir, file_paths=file_paths)
379
+
380
+
381
+ # ---------------------------------------------------------------------------
382
+ # Projection: route_surface
383
+ # ---------------------------------------------------------------------------
384
+
385
+ def project_route_surface(cir: CanonicalRepositoryIR) -> list[dict]:
386
+ """Project CIR endpoints to route_surface format.
387
+
388
+ Canonical derivation — no independent endpoint data reconstruction.
389
+ Output format compatible with existing route_surface consumers.
390
+ """
391
+ routes: list[dict] = []
392
+ for ep in cir.endpoints:
393
+ entry: dict = {
394
+ "symbol": ep.handler_symbol,
395
+ "controller": ep.controller_class,
396
+ "declaring_class": ep.controller_class,
397
+ "effective_class": ep.controller_class,
398
+ "path": ep.path,
399
+ "method": ep.method,
400
+ "stable_id": ep.stable_id,
401
+ "inheritance_depth": ep.inheritance_depth,
402
+ }
403
+ if ep.security is not None:
404
+ # Keep security_annotations key for backward compat with route_surface consumers
405
+ entry["security_annotations"] = ep.security.to_dict()
406
+ routes.append(entry)
407
+
408
+ return sorted(routes, key=lambda r: (r["effective_class"], r["path"]))
409
+
410
+
411
+ # ---------------------------------------------------------------------------
412
+ # Projection: endpoint_surface (canonical replacement for extract_java_endpoints)
413
+ # ---------------------------------------------------------------------------
414
+
415
+ def project_endpoint_surface(cir: CanonicalRepositoryIR) -> dict:
416
+ """Project CIR to endpoint surface format.
417
+
418
+ Replaces extract_java_endpoints as the canonical endpoint extractor.
419
+ Output format is backward compatible with extract_java_endpoints.
420
+
421
+ Source: cir.endpoints — never independently parsed from Java source.
422
+ Security: cir.security_index — never independently re-extracted.
423
+ """
424
+ endpoints: list[dict] = []
425
+
426
+ for ep in cir.endpoints:
427
+ # Simple names for backward compat (same as extract_java_endpoints output)
428
+ controller_simple = ep.controller_class.split(".")[-1]
429
+ handler_simple = (
430
+ ep.handler_symbol.split("#")[1]
431
+ if "#" in ep.handler_symbol
432
+ else ep.handler_symbol.rsplit(".", 1)[-1]
433
+ )
434
+
435
+ entry: dict = {
436
+ "method": ep.method,
437
+ "path": ep.path,
438
+ "controller": controller_simple,
439
+ "handler": handler_simple,
440
+ }
441
+
442
+ if ep.security is not None:
443
+ entry["security"] = ep.security.to_dict()
444
+ # Backward compat: top-level required_permission for custom annotation
445
+ if ep.security.policy == "custom_permission":
446
+ entry["required_permission"] = ep.security.required_permission
447
+
448
+ endpoints.append(entry)
449
+
450
+ no_security_signal = sum(1 for e in endpoints if "security" not in e)
451
+ return {
452
+ "endpoints": endpoints,
453
+ "total": len(endpoints),
454
+ "no_security_signal": no_security_signal,
455
+ # Legacy field alias — same count, kept for backward compat
456
+ "undocumented": no_security_signal,
457
+ }
458
+
459
+
460
+ # ---------------------------------------------------------------------------
461
+ # Projection: blast_radius
462
+ # ---------------------------------------------------------------------------
463
+
464
+ def project_blast_radius(
465
+ cir: CanonicalRepositoryIR,
466
+ target: str,
467
+ *,
468
+ max_depth: int = 4,
469
+ ) -> dict:
470
+ """Project blast radius from CIR.
471
+
472
+ Delegates BFS logic to compute_blast_radius. Uses _raw_ir which has the
473
+ canonical route_surface already built from the same symbol extraction pass.
474
+
475
+ All endpoints in the result come from cir.endpoints (via route_surface).
476
+ No independent endpoint reconstruction.
477
+ """
478
+ return _compute_blast_radius(cir._raw_ir, target, max_depth=max_depth)
479
+
480
+
481
+ # ---------------------------------------------------------------------------
482
+ # Invariant validator
483
+ # ---------------------------------------------------------------------------
484
+
485
+ def validate_canonical_ir(cir: CanonicalRepositoryIR) -> list[str]:
486
+ """Validate CIR invariants. Returns list of violation strings.
487
+
488
+ Empty list means CIR is valid.
489
+
490
+ Invariants:
491
+ 1. Schema version matches IR_SCHEMA_VERSION
492
+ 2. Endpoint consistency: no duplicate IDs, required fields present
493
+ 3. Security index consistency: all keys are valid handler_symbols
494
+ 4. Graph consistency: reverse_graph entries correspond to call_graph edges
495
+ 5. Determinism: cir_hash recomputes identically
496
+ 6. Blast radius endpoints: endpoints_affected ⊆ cir.endpoints (sampled)
497
+ """
498
+ violations: list[str] = []
499
+
500
+ # ── 1. Schema version ────────────────────────────────────────────────────
501
+ if cir.schema_version != IR_SCHEMA_VERSION:
502
+ violations.append(
503
+ f"SCHEMA: version mismatch: stored='{cir.schema_version}' "
504
+ f"expected='{IR_SCHEMA_VERSION}'"
505
+ )
506
+
507
+ # ── 2. Endpoint consistency ──────────────────────────────────────────────
508
+ endpoint_ids: set[str] = set()
509
+ handler_symbols: set[str] = set()
510
+ for ep in cir.endpoints:
511
+ # Duplicate IDs
512
+ if ep.id in endpoint_ids:
513
+ violations.append(f"ENDPOINT: duplicate id={ep.id!r}")
514
+ endpoint_ids.add(ep.id)
515
+
516
+ # Required fields
517
+ if not ep.method:
518
+ violations.append(f"ENDPOINT: id={ep.id!r} missing method")
519
+ if not ep.path:
520
+ violations.append(f"ENDPOINT: id={ep.id!r} missing path")
521
+ if not ep.controller_class:
522
+ violations.append(f"ENDPOINT: id={ep.id!r} missing controller_class")
523
+ if not ep.handler_symbol:
524
+ violations.append(f"ENDPOINT: id={ep.id!r} missing handler_symbol")
525
+ else:
526
+ handler_symbols.add(ep.handler_symbol)
527
+
528
+ # ID must be deterministically reconstructible
529
+ expected_id = CanonicalEndpoint.make_id(
530
+ ep.method, ep.path, ep.controller_class, ep.handler_symbol
531
+ )
532
+ if ep.id != expected_id:
533
+ violations.append(
534
+ f"ENDPOINT: id not deterministic: stored={ep.id!r} "
535
+ f"recomputed={expected_id!r}"
536
+ )
537
+
538
+ # ── 3. Security index consistency ────────────────────────────────────────
539
+ for sym in cir.security_index:
540
+ if sym not in handler_symbols:
541
+ violations.append(
542
+ f"SECURITY_INDEX: key {sym!r} not in endpoint handler_symbols"
543
+ )
544
+
545
+ # ── 4. Graph consistency (sampled — max 500 edges) ───────────────────────
546
+ # Build forward edge set from call_graph for reverse-graph validation
547
+ call_graph_edge_set: set[tuple[str, str, str]] = {
548
+ (e.get("from", ""), e.get("to", ""), e.get("type", ""))
549
+ for e in cir.call_graph
550
+ }
551
+
552
+ rg_checked = 0
553
+ for target_sym, by_type in sorted(cir.reverse_graph.items()):
554
+ for edge_type, callers in sorted(by_type.items()):
555
+ if edge_type in _REVERSE_EXCLUDE:
556
+ continue
557
+ for caller in sorted(callers):
558
+ if (caller, target_sym, edge_type) not in call_graph_edge_set:
559
+ violations.append(
560
+ f"GRAPH: reverse_graph edge "
561
+ f"{caller!r} →[{edge_type}]→ {target_sym!r} "
562
+ f"not in call_graph"
563
+ )
564
+ rg_checked += 1
565
+ if rg_checked >= 500:
566
+ break
567
+ if rg_checked >= 500:
568
+ break
569
+ if rg_checked >= 500:
570
+ break
571
+
572
+ # ── 5. Determinism: recompute hash ───────────────────────────────────────
573
+ recomputed = _compute_cir_hash(
574
+ cir.schema_version, cir.files, cir.symbols, cir.endpoints, cir.call_graph
575
+ )
576
+ if recomputed != cir.cir_hash:
577
+ violations.append(
578
+ f"DETERMINISM: cir_hash mismatch: "
579
+ f"stored='{cir.cir_hash[:16]}...' "
580
+ f"recomputed='{recomputed[:16]}...'"
581
+ )
582
+
583
+ return violations