sourcecode 1.31.6__tar.gz → 1.31.8__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 (224) hide show
  1. {sourcecode-1.31.6 → sourcecode-1.31.8}/PKG-INFO +3 -3
  2. {sourcecode-1.31.6 → sourcecode-1.31.8}/README.md +2 -2
  3. {sourcecode-1.31.6 → sourcecode-1.31.8}/pyproject.toml +1 -1
  4. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/architecture_analyzer.py +1 -1
  6. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/cli.py +56 -1
  7. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/prepare_context.py +35 -0
  8. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/repository_ir.py +193 -7
  9. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/runtime_classifier.py +79 -1
  10. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/serializer.py +58 -0
  11. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-0911b79e.json +0 -3825
  12. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-37df4554.json +0 -266
  13. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-624321f3.json +0 -12401
  14. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-776b4676.json +0 -386
  15. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-9770fba7.json +0 -373
  16. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-c4e3c102.json +0 -266
  17. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-e8bc5fb4.json +0 -122
  18. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-e9801942.json +0 -219
  19. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-ee60e0cd.json +0 -318
  20. sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-fdd9d3f7.json +0 -552
  21. {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  22. {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  23. {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  24. {sourcecode-1.31.6 → sourcecode-1.31.8}/.continue-here.md +0 -0
  25. {sourcecode-1.31.6 → sourcecode-1.31.8}/.github/workflows/build-windows.yml +0 -0
  26. {sourcecode-1.31.6 → sourcecode-1.31.8}/.gitignore +0 -0
  27. {sourcecode-1.31.6 → sourcecode-1.31.8}/.ruff.toml +0 -0
  28. {sourcecode-1.31.6 → sourcecode-1.31.8}/CHANGELOG.md +0 -0
  29. {sourcecode-1.31.6 → sourcecode-1.31.8}/CONTRIBUTING.md +0 -0
  30. {sourcecode-1.31.6 → sourcecode-1.31.8}/LICENSE +0 -0
  31. {sourcecode-1.31.6 → sourcecode-1.31.8}/SECURITY.md +0 -0
  32. {sourcecode-1.31.6 → sourcecode-1.31.8}/docs/privacy.md +0 -0
  33. {sourcecode-1.31.6 → sourcecode-1.31.8}/docs/schema.md +0 -0
  34. {sourcecode-1.31.6 → sourcecode-1.31.8}/raw +0 -0
  35. {sourcecode-1.31.6 → sourcecode-1.31.8}/run_cli.py +0 -0
  36. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/adaptive_scanner.py +0 -0
  37. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/architecture_summary.py +0 -0
  38. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/ast_extractor.py +0 -0
  39. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/classifier.py +0 -0
  40. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/code_notes_analyzer.py +0 -0
  41. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/confidence_analyzer.py +0 -0
  42. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/context_scorer.py +0 -0
  43. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/context_summarizer.py +0 -0
  44. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/contract_model.py +0 -0
  45. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/contract_pipeline.py +0 -0
  46. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/coverage_parser.py +0 -0
  47. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/dependency_analyzer.py +0 -0
  48. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/__init__.py +0 -0
  49. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/base.py +0 -0
  50. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/csproj_parser.py +0 -0
  51. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/dart.py +0 -0
  52. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/dotnet.py +0 -0
  53. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/elixir.py +0 -0
  54. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/go.py +0 -0
  55. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/heuristic.py +0 -0
  56. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/hybrid.py +0 -0
  57. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/java.py +0 -0
  58. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/jvm_ext.py +0 -0
  59. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/nodejs.py +0 -0
  60. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/parsers.py +0 -0
  61. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/php.py +0 -0
  62. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/project.py +0 -0
  63. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/python.py +0 -0
  64. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/ruby.py +0 -0
  65. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/rust.py +0 -0
  66. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/systems.py +0 -0
  67. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/terraform.py +0 -0
  68. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/tooling.py +0 -0
  69. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/doc_analyzer.py +0 -0
  70. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/entrypoint_classifier.py +0 -0
  71. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/env_analyzer.py +0 -0
  72. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/file_classifier.py +0 -0
  73. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/flow_analyzer.py +0 -0
  74. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/git_analyzer.py +0 -0
  75. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/graph_analyzer.py +0 -0
  76. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/__init__.py +0 -0
  77. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  78. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  79. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  80. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  81. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  82. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/runner.py +0 -0
  83. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/server.py +0 -0
  84. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/metrics_analyzer.py +0 -0
  85. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/path_filters.py +0 -0
  86. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/pr_comment_renderer.py +0 -0
  87. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/progress.py +0 -0
  88. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/ranking_engine.py +0 -0
  89. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/redactor.py +0 -0
  90. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/relevance_scorer.py +0 -0
  91. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/repo_classifier.py +0 -0
  92. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/scanner.py +0 -0
  93. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/schema.py +0 -0
  94. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/semantic_analyzer.py +0 -0
  95. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/summarizer.py +0 -0
  96. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/__init__.py +0 -0
  97. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/config.py +0 -0
  98. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/consent.py +0 -0
  99. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/events.py +0 -0
  100. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/filters.py +0 -0
  101. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/transport.py +0 -0
  102. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/tree_utils.py +0 -0
  103. {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/workspace.py +0 -0
  104. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/__init__.py +0 -0
  105. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/conftest.py +0 -0
  106. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/coverage.xml +0 -0
  107. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  108. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/fastapi_app/src/main.py +0 -0
  109. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  110. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/go_service/go.mod +0 -0
  111. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/jacoco.xml +0 -0
  112. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/latin1_sample.java +0 -0
  113. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/latin1_sample_iso.java +0 -0
  114. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/lcov.info +0 -0
  115. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  116. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/package.json +0 -0
  117. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  118. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  119. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  120. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  121. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  122. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  123. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  124. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  125. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  126. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  127. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  128. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  129. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  130. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  131. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  132. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  133. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  134. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  135. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  136. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  137. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  138. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  139. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  140. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  141. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  142. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  143. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  144. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  145. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  146. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  147. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  148. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  149. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  150. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_architecture_analyzer.py +0 -0
  151. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_architecture_summary.py +0 -0
  152. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_ast_extractor.py +0 -0
  153. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block1_reliability.py +0 -0
  154. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block2_coverage.py +0 -0
  155. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block5_quality.py +0 -0
  156. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_broadleaf_fixes.py +0 -0
  157. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1302.py +0 -0
  158. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1312.py +0 -0
  159. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1313.py +0 -0
  160. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v16.py +0 -0
  161. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v2.py +0 -0
  162. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_classifier.py +0 -0
  163. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_cli.py +0 -0
  164. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_code_notes_analyzer.py +0 -0
  165. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_context_scorer.py +0 -0
  166. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_contract_pipeline.py +0 -0
  167. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_coverage_parser.py +0 -0
  168. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_cross_consistency.py +0 -0
  169. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_analyzer_node_python.py +0 -0
  170. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_analyzer_polyglot.py +0 -0
  171. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_schema.py +0 -0
  172. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_dotnet.py +0 -0
  173. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_go_rust_java.py +0 -0
  174. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_nodejs.py +0 -0
  175. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_php_ruby_dart.py +0 -0
  176. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_python.py +0 -0
  177. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_universal_managed.py +0 -0
  178. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_universal_systems.py +0 -0
  179. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detectors_base.py +0 -0
  180. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_doc_analyzer_jsdom.py +0 -0
  181. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_doc_analyzer_python.py +0 -0
  182. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_encoding_regression.py +0 -0
  183. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_analyzer_polyglot.py +0 -0
  184. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_analyzer_python_node.py +0 -0
  185. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_schema.py +0 -0
  186. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_hybrid_inference.py +0 -0
  187. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration.py +0 -0
  188. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_dependencies.py +0 -0
  189. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_detection.py +0 -0
  190. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_docs.py +0 -0
  191. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_graph_modules.py +0 -0
  192. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_lqn.py +0 -0
  193. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_metrics.py +0 -0
  194. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_multistack.py +0 -0
  195. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_semantics.py +0 -0
  196. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_universal.py +0 -0
  197. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_java_spring_integration.py +0 -0
  198. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_runner.py +0 -0
  199. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_serve.py +0 -0
  200. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_tools.py +0 -0
  201. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_metrics_analyzer.py +0 -0
  202. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_output_ux.py +0 -0
  203. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_packaging.py +0 -0
  204. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_phase1_improvements.py +0 -0
  205. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_pipeline_integrity.py +0 -0
  206. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_real_projects.py +0 -0
  207. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_redactor.py +0 -0
  208. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_repository_ir.py +0 -0
  209. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_scanner.py +0 -0
  210. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_schema.py +0 -0
  211. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_schema_normalization.py +0 -0
  212. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_scoring_calibration.py +0 -0
  213. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_analyzer_node.py +0 -0
  214. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_analyzer_python.py +0 -0
  215. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_import_resolution.py +0 -0
  216. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_schema.py +0 -0
  217. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_signal_hierarchy.py +0 -0
  218. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_summarizer.py +0 -0
  219. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_surface_honesty.py +0 -0
  220. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_task_differentiation.py +0 -0
  221. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_telemetry.py +0 -0
  222. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_v131_improvements.py +0 -0
  223. {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_v1_10_regressions.py +0 -0
  224. {sourcecode-1.31.6 → sourcecode-1.31.8}/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.6
3
+ Version: 1.31.8
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.6-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.8-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.6
264
+ # sourcecode 1.31.8
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.6-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.31.8-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.6
41
+ # sourcecode 1.31.8
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.6"
7
+ version = "1.31.8"
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.6"
3
+ __version__ = "1.31.8"
@@ -330,7 +330,7 @@ class ArchitectureAnalyzer:
330
330
  tentative = True
331
331
  if not _hard_evidence:
332
332
  limitations.append(
333
- "Pattern not confirmed by module import graph; run with --graph-modules for structural validation"
333
+ "Pattern inferred from directory structure; import graph not available structural dependency direction unverified"
334
334
  )
335
335
  if not _hard_evidence:
336
336
  matched_dirs = sorted({
@@ -836,6 +836,7 @@ def main(
836
836
  code_notes = True
837
837
  no_tree = True # agents never need the raw file tree
838
838
  architecture = True # agents need full architectural signal (M4)
839
+ graph_modules = True # IC-003: import graph needed for architecture confidence
839
840
 
840
841
  # ── GAP-9: Cache check — serve from .sourcecode-cache when git SHA unchanged ──
841
842
  import hashlib as _hashlib
@@ -2495,6 +2496,10 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2495
2496
 
2496
2497
  endpoints: list[dict] = []
2497
2498
  seen: set[tuple] = set()
2499
+ # Fix 1: inheritance projection — tracks class data so controllers with ONLY
2500
+ # inherited endpoints (no own @RequestMapping methods) are not silently dropped.
2501
+ _class_info: dict[str, dict] = {}
2502
+ _EXTENDS_RE_LOCAL = _re.compile(r'\bextends\s+(\w+)')
2498
2503
 
2499
2504
  from sourcecode.path_filters import is_test_path as _is_test_path
2500
2505
 
@@ -2579,14 +2584,18 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2579
2584
  # Extract class-level base path and locate class body start
2580
2585
  lines = content.splitlines()
2581
2586
 
2582
- # First pass: find class/interface declaration line index
2587
+ # First pass: find class/interface declaration line index and extends clause.
2583
2588
  class_body_start = 0
2589
+ extends_class_name: Optional[str] = None
2584
2590
  for i, line in enumerate(lines):
2585
2591
  stripped_l = line.strip()
2586
2592
  if (not stripped_l.startswith("//") and not stripped_l.startswith("*")
2587
2593
  and ("class " in stripped_l or "interface " in stripped_l)
2588
2594
  and _CLASS_RE.search(stripped_l)):
2589
2595
  class_body_start = i + 1
2596
+ _ext_m = _EXTENDS_RE_LOCAL.search(stripped_l)
2597
+ if _ext_m:
2598
+ extends_class_name = _ext_m.group(1)
2590
2599
  break
2591
2600
 
2592
2601
  # Second pass: extract class-level @RequestMapping path (only before class body).
@@ -2656,6 +2665,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2656
2665
  pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
2657
2666
  pending_filtro: Optional[str] = None
2658
2667
  in_block_comment = False
2668
+ file_own_endpoints: list[tuple] = [] # (http_verb, path_suffix, handler, filtro)
2659
2669
 
2660
2670
  for i in range(class_body_start, len(lines)):
2661
2671
  line = lines[i]
@@ -2708,6 +2718,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2708
2718
  handler = mm.group(1) if mm else ""
2709
2719
  if handler and not handler.startswith("class"):
2710
2720
  for http_verb, path_suffix in pending_annotations:
2721
+ file_own_endpoints.append((http_verb, path_suffix, handler, pending_filtro))
2711
2722
  for _cb in class_bases: # Bug #1B: one endpoint per class prefix
2712
2723
  full_path = (_cb + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
2713
2724
  if not full_path.startswith("/"):
@@ -2733,6 +2744,50 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2733
2744
  pending_annotations = []
2734
2745
  pending_filtro = None
2735
2746
 
2747
+ # Store per-class data for inheritance projection
2748
+ _class_info[class_name] = {
2749
+ "base_paths": class_bases,
2750
+ "extends_class": extends_class_name,
2751
+ "own_endpoints": file_own_endpoints,
2752
+ }
2753
+
2754
+ # Fix 1: Inheritance projection — controllers whose methods are 100% inherited from a
2755
+ # base class emit 0 endpoints above because no method-level annotations exist in their
2756
+ # file. Walk the inheritance chain and project parent suffixes with the child's base path.
2757
+ for _cls, _data in _class_info.items():
2758
+ if _data["own_endpoints"]:
2759
+ continue
2760
+ _bases = _data["base_paths"]
2761
+ if not _bases or _bases == [""]:
2762
+ continue
2763
+ _chain = _data.get("extends_class")
2764
+ _visited: set[str] = {_cls}
2765
+ while _chain and _chain not in _visited:
2766
+ _visited.add(_chain)
2767
+ _parent = _class_info.get(_chain)
2768
+ if not _parent:
2769
+ break
2770
+ if _parent["own_endpoints"]:
2771
+ for _verb, _suffix, _handler, _filtro in _parent["own_endpoints"]:
2772
+ for _cb in _bases:
2773
+ _fp = (_cb + "/" + _suffix).replace("//", "/").rstrip("/") or "/"
2774
+ if not _fp.startswith("/"):
2775
+ _fp = "/" + _fp
2776
+ _key = (_cls, _handler, _verb, _cb)
2777
+ if _key not in seen:
2778
+ seen.add(_key)
2779
+ _entry: dict[str, Any] = {
2780
+ "method": _verb,
2781
+ "path": _fp,
2782
+ "controller": _cls,
2783
+ "handler": _handler,
2784
+ }
2785
+ if _filtro:
2786
+ _entry["required_permission"] = _filtro
2787
+ endpoints.append(_entry)
2788
+ break
2789
+ _chain = _parent.get("extends_class")
2790
+
2736
2791
  endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
2737
2792
  undocumented = sum(1 for e in endpoints if "required_permission" not in e)
2738
2793
 
@@ -1212,6 +1212,41 @@ class TaskContextBuilder:
1212
1212
  symptom=symptom if task_name == "fix-bug" else None,
1213
1213
  )
1214
1214
 
1215
+ # ── IC-006: fix-bug suspected_areas — recompute from ranked files + bug notes ──
1216
+ # relevant_files is now ranked by RankingEngine (git churn, fan_in, centrality, notes).
1217
+ # suspected_areas should reflect that ranking, not raw comment count.
1218
+ if task_name == "fix-bug" and relevant_files:
1219
+ _bug_kinds = {"FIXME", "BUG", "HACK", "XXX"}
1220
+ _bug_counts: dict[str, int] = {}
1221
+ for _note in cn_notes_for_ranking:
1222
+ if _note.kind in _bug_kinds:
1223
+ _bug_counts[_note.path] = _bug_counts.get(_note.path, 0) + 1
1224
+
1225
+ # Primary: top-ranked RelevantFile objects (dataclass, use .path/.reason)
1226
+ _ranked_paths = [rf.path for rf in relevant_files[:30]]
1227
+ _primary: list[str] = []
1228
+ for rf in relevant_files[:30]:
1229
+ p = rf.path
1230
+ if not p:
1231
+ continue
1232
+ n = _bug_counts.get(p, 0)
1233
+ reason_str = str(rf.reason) if rf.reason else ""
1234
+ note_str = f" ({n} bug annotation{'s' if n > 1 else ''})" if n else ""
1235
+ _primary.append(f"{p}{note_str}" + (f" — {reason_str}" if reason_str else ""))
1236
+ if len(_primary) >= 5:
1237
+ break
1238
+
1239
+ # Secondary: remaining high-note files not already in primary
1240
+ _ranked_set = set(_ranked_paths[:len(_primary)])
1241
+ _secondary = [
1242
+ f"{p} ({n} annotation{'s' if n > 1 else ''})"
1243
+ for p, n in sorted(_bug_counts.items(), key=lambda x: -x[1])
1244
+ if p not in _ranked_set
1245
+ ][:3]
1246
+
1247
+ if _primary or _secondary:
1248
+ suspected_areas = _primary + _secondary
1249
+
1215
1250
  # ── 6b. review-pr: derive PR-specific impact sections from delta analysis ──
1216
1251
  _pr_security_impact: dict = {}
1217
1252
  _pr_transactional_impact: dict = {}
@@ -178,8 +178,11 @@ _SPRING_OTHER: frozenset[str] = frozenset({
178
178
  "@PutMapping", "@DeleteMapping", "@PatchMapping", "@Autowired",
179
179
  "@Inject", "@Value", "@Qualifier", "@EnableWebSecurity",
180
180
  "@SpringBootApplication", "@EnableAutoConfiguration",
181
+ "@EventListener", "@Async", "@Scheduled", "@Cacheable", "@CacheEvict",
181
182
  })
182
183
 
184
+ _PUBLISH_EVENT_RE = re.compile(r'\.publishEvent\s*\(\s*new\s+(\w+)\s*[(\{]')
185
+
183
186
  _HTTP_METHOD_MAP: dict[str, str] = {
184
187
  "@GetMapping": "GET",
185
188
  "@PostMapping": "POST",
@@ -787,6 +790,33 @@ def _build_relations(
787
790
  evidence={"type": "structural", "value": f"member of {enclosing}"},
788
791
  ))
789
792
 
793
+ # Fix 5: Event flow edges — publishEvent publishers and @EventListener subscribers.
794
+ # listens_to_event: method with @EventListener → resolved event parameter type(s).
795
+ for sym in symbols:
796
+ if sym.type == "method" and "@EventListener" in sym.annotations:
797
+ for imp_fqn in sym.imports_used:
798
+ edges.append(RelationEdge(
799
+ from_symbol=sym.symbol,
800
+ to_symbol=imp_fqn,
801
+ type="listens_to_event",
802
+ confidence="high",
803
+ evidence={"type": "annotation", "value": "@EventListener"},
804
+ ))
805
+
806
+ # publishes_event: class that calls publishEvent(new XxxEvent(...)) → event type FQN.
807
+ _class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
808
+ for m in _PUBLISH_EVENT_RE.finditer(source):
809
+ event_simple = m.group(1)
810
+ event_fqn = import_map.get(event_simple, event_simple)
811
+ for cls_sym in _class_syms:
812
+ edges.append(RelationEdge(
813
+ from_symbol=cls_sym.symbol,
814
+ to_symbol=event_fqn,
815
+ type="publishes_event",
816
+ confidence="medium",
817
+ evidence={"type": "method_call", "value": f"publishEvent(new {event_simple})"},
818
+ ))
819
+
790
820
  seen: set[tuple[str, str, str]] = set()
791
821
  unique: list[RelationEdge] = []
792
822
  for e in edges:
@@ -1190,6 +1220,8 @@ _EDGE_REASON_TEMPLATES: dict[str, str] = {
1190
1220
  "contained_in": "{from_sym} is a member of {to_sym}",
1191
1221
  "annotated_with": "{from_sym} is annotated with {to_sym}",
1192
1222
  "mapped_to": "Route {to_sym} depends on {from_sym}",
1223
+ "publishes_event": "{from_sym} publishes event {to_sym}",
1224
+ "listens_to_event": "{from_sym} listens for event {to_sym}",
1193
1225
  }
1194
1226
 
1195
1227
  # Edge types to exclude from reverse impact traversal (too noisy / non-dependency semantics)
@@ -1505,6 +1537,18 @@ def _assemble(
1505
1537
  by_type.setdefault(e.type, []).append(e.from_symbol)
1506
1538
  reverse_graph_out[target] = by_type
1507
1539
 
1540
+ # IC-005: aggregate event flow edges already built in _build_relations
1541
+ _listen_edges = [e for e in sorted_rels if e.type == "listens_to_event"]
1542
+ _publish_edges = [e for e in sorted_rels if e.type == "publishes_event"]
1543
+ _spring_events: Optional[dict] = None
1544
+ if _listen_edges or _publish_edges:
1545
+ _spring_events = {
1546
+ "listeners": sorted({e.from_symbol for e in _listen_edges}),
1547
+ "publishers": sorted({e.from_symbol for e in _publish_edges}),
1548
+ "event_types": sorted({e.to_symbol for e in _listen_edges + _publish_edges}),
1549
+ "flow_count": len(_listen_edges) + len(_publish_edges),
1550
+ }
1551
+
1508
1552
  return {
1509
1553
  "schema_version": "final-v1",
1510
1554
  "graph": {
@@ -1526,13 +1570,136 @@ def _assemble(
1526
1570
  },
1527
1571
  "subsystems": subsystems,
1528
1572
  "change_set": change_set_out,
1529
- "route_surface": route_diffs or [],
1573
+ "route_surface": _build_route_surface(sorted_syms, route_diffs, extends_map={
1574
+ e.from_symbol: e.to_symbol.split(".")[-1]
1575
+ for e in sorted_rels if e.type == "extends"
1576
+ }),
1577
+ **({"spring_events": _spring_events} if _spring_events else {}),
1530
1578
  "audit": {
1531
1579
  "dropped_fields": dropped_fields,
1532
1580
  },
1533
1581
  }
1534
1582
 
1535
1583
 
1584
+ # ---------------------------------------------------------------------------
1585
+ # Route surface helper (Fix 4)
1586
+ # ---------------------------------------------------------------------------
1587
+
1588
+ def _build_route_surface(
1589
+ symbols: list[SymbolRecord],
1590
+ route_diffs: Optional[list[dict]],
1591
+ extends_map: Optional[dict[str, str]] = None,
1592
+ ) -> list[dict]:
1593
+ """Return route surface with inheritance projection.
1594
+
1595
+ extends_map: child_fqn → parent_simple_name derived from RelationEdge extends edges.
1596
+ Projects inherited endpoints onto subclasses that have a class-level @RequestMapping
1597
+ prefix but zero own method-level endpoints (IC-001 fix).
1598
+ """
1599
+ if route_diffs:
1600
+ return route_diffs
1601
+
1602
+ # Phase 1: build per-class metadata (prefix) and own endpoint list
1603
+ class_info: dict[str, dict] = {} # simple_name → {fqn, prefix, own_endpoints}
1604
+ for sym in symbols:
1605
+ if sym.type not in ("class", "interface"):
1606
+ continue
1607
+ simple = sym.symbol.split(".")[-1]
1608
+ prefix = ""
1609
+ if "@RequestMapping" in sym.annotations:
1610
+ args = sym.annotation_values.get("@RequestMapping", "")
1611
+ prefix = _parse_route_path(args)
1612
+ class_info[simple] = {"fqn": sym.symbol, "prefix": prefix, "own_endpoints": []}
1613
+
1614
+ routes: list[dict] = []
1615
+ seen: set[tuple] = set()
1616
+
1617
+ # Phase 2: emit own endpoint symbols and record them per class
1618
+ for sym in symbols:
1619
+ if sym.symbol_kind != "endpoint":
1620
+ continue
1621
+ ann_name = next((a for a in sym.annotations if a in _ENDPOINT_ANNOTATIONS), None)
1622
+ if not ann_name:
1623
+ continue
1624
+ cls_fqn = _enclosing_class(sym.symbol)
1625
+ cls_simple = cls_fqn.split(".")[-1]
1626
+ args = sym.annotation_values.get(ann_name, "")
1627
+ suffix = _parse_route_path(args)
1628
+ method = _parse_route_http_method(ann_name, args) or "GET"
1629
+
1630
+ if cls_simple in class_info:
1631
+ class_info[cls_simple]["own_endpoints"].append(
1632
+ (method, suffix, sym.symbol, sym.stable_id)
1633
+ )
1634
+
1635
+ prefix = class_info.get(cls_simple, {}).get("prefix", "")
1636
+ full_path = (prefix + "/" + suffix).replace("//", "/").rstrip("/") or "/"
1637
+ if not full_path.startswith("/"):
1638
+ full_path = "/" + full_path
1639
+
1640
+ key = (sym.symbol, method, prefix)
1641
+ if key not in seen:
1642
+ seen.add(key)
1643
+ routes.append({
1644
+ "symbol": sym.symbol,
1645
+ "controller": cls_fqn,
1646
+ "declaring_class": cls_fqn,
1647
+ "effective_class": cls_fqn,
1648
+ "path": full_path,
1649
+ "method": method,
1650
+ "stable_id": sym.stable_id,
1651
+ "inheritance_depth": 0,
1652
+ })
1653
+
1654
+ # Phase 3: inheritance projection — subclasses with zero own endpoints
1655
+ # but with a class-level @RequestMapping prefix inherit parent methods.
1656
+ if extends_map:
1657
+ fqn_to_simple: dict[str, str] = {d["fqn"]: s for s, d in class_info.items()}
1658
+ simple_extends: dict[str, str] = {
1659
+ fqn_to_simple.get(child_fqn, child_fqn.split(".")[-1]): parent_simple
1660
+ for child_fqn, parent_simple in extends_map.items()
1661
+ }
1662
+
1663
+ for cls_simple, data in class_info.items():
1664
+ if data["own_endpoints"]:
1665
+ continue
1666
+ if not data["prefix"]:
1667
+ continue
1668
+
1669
+ chain = simple_extends.get(cls_simple)
1670
+ visited: set[str] = {cls_simple}
1671
+ depth = 1
1672
+ while chain and chain not in visited:
1673
+ visited.add(chain)
1674
+ parent = class_info.get(chain)
1675
+ if not parent:
1676
+ break
1677
+ if parent["own_endpoints"]:
1678
+ for verb, suffix, declaring_sym, stable_id in parent["own_endpoints"]:
1679
+ prefix = data["prefix"]
1680
+ full_path = (prefix + "/" + suffix).replace("//", "/").rstrip("/") or "/"
1681
+ if not full_path.startswith("/"):
1682
+ full_path = "/" + full_path
1683
+ key = (cls_simple, declaring_sym, verb, prefix)
1684
+ if key not in seen:
1685
+ seen.add(key)
1686
+ routes.append({
1687
+ "symbol": declaring_sym,
1688
+ "controller": data["fqn"],
1689
+ "declaring_class": parent["fqn"],
1690
+ "effective_class": data["fqn"],
1691
+ "path": full_path,
1692
+ "method": verb,
1693
+ "stable_id": stable_id,
1694
+ "inheritance_depth": depth,
1695
+ })
1696
+ break
1697
+ chain = simple_extends.get(chain)
1698
+ depth += 1
1699
+
1700
+ return sorted(routes, key=lambda r: (r["effective_class"], r["path"]))
1701
+
1702
+
1536
1703
  # ---------------------------------------------------------------------------
1537
1704
  # Public API
1538
1705
  # ---------------------------------------------------------------------------
@@ -1675,7 +1842,17 @@ def apply_ir_size_limits(
1675
1842
  "remove --summary-only to restore full graph"
1676
1843
  ),
1677
1844
  }
1678
- out["reverse_graph"] = {}
1845
+ # Fix 3: keep bounded reverse graph instead of wiping it.
1846
+ full_rg: dict = ir.get("reverse_graph") or {}
1847
+ if full_rg:
1848
+ _rg_sorted = sorted(
1849
+ full_rg.items(),
1850
+ key=lambda x: sum(len(v) for v in x[1].values()),
1851
+ reverse=True,
1852
+ )
1853
+ out["reverse_graph"] = dict(_rg_sorted[:50])
1854
+ else:
1855
+ out["reverse_graph"] = {}
1679
1856
  out["impact"] = {
1680
1857
  "global_score": (ir.get("impact") or {}).get("global_score", 0),
1681
1858
  "ranked_nodes": ranked[:20],
@@ -1703,10 +1880,19 @@ def apply_ir_size_limits(
1703
1880
 
1704
1881
  if kept_fqns is not None or max_edges is not None:
1705
1882
  if kept_fqns is not None:
1706
- # Priority: edges where both endpoints are kept nodes
1707
- priority = [e for e in edges if e["from"] in kept_fqns and e["to"] in kept_fqns]
1708
- rest = [e for e in edges if not (e["from"] in kept_fqns and e["to"] in kept_fqns)]
1709
- edges = priority + rest
1883
+ # Fix 2: type-aware priority so semantic edges survive node truncation.
1884
+ # Annotation strings (@Service etc.) and field FQNs are never in kept_fqns,
1885
+ # so "both endpoints kept" drops all injects/annotated_with edges.
1886
+ _SEMANTIC_TYPES = frozenset({"extends", "implements", "injects",
1887
+ "publishes_event", "listens_to_event"})
1888
+ _ANNOTATION_TYPES = frozenset({"annotated_with"})
1889
+ tier1 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _SEMANTIC_TYPES]
1890
+ tier2 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _ANNOTATION_TYPES]
1891
+ tier3 = [e for e in edges
1892
+ if e["from"] in kept_fqns and e["to"] in kept_fqns and e["type"] == "imports"]
1893
+ _seen_e = {(e["from"], e["to"], e["type"]) for e in tier1 + tier2 + tier3}
1894
+ tier4 = [e for e in edges if (e["from"], e["to"], e["type"]) not in _seen_e]
1895
+ edges = tier1 + tier2 + tier3 + tier4
1710
1896
  if max_edges is not None:
1711
1897
  edges = edges[:max_edges]
1712
1898
 
@@ -1722,7 +1908,7 @@ def apply_ir_size_limits(
1722
1908
  # Convenience: find Java files in a repo
1723
1909
  # ---------------------------------------------------------------------------
1724
1910
 
1725
- def find_java_files(root: Path, *, max_files: int = 500) -> list[str]:
1911
+ def find_java_files(root: Path, *, max_files: int = 3000) -> list[str]:
1726
1912
  """Return relative paths to Java files under root, excluding test dirs and vendor."""
1727
1913
  results: list[str] = []
1728
1914
  for p in sorted(root.rglob("*.java")):
@@ -141,6 +141,11 @@ class RuntimeClassifier:
141
141
  raw.append((ws_path, self._load_pkg_json(pkg_root)))
142
142
 
143
143
  fan_in = self._compute_fan_in(raw)
144
+ # IC-004: merge Maven pom.xml cross-module fan_in when no JS deps detected
145
+ maven_fan_in = self._compute_maven_fan_in(root, workspace_paths)
146
+ for ws_path, count in maven_fan_in.items():
147
+ fan_in[ws_path] = fan_in.get(ws_path, 0) + count
148
+
144
149
  results: list[MonorepoPackageInfo] = []
145
150
 
146
151
  for ws_path, pkg_json in raw:
@@ -280,6 +285,9 @@ class RuntimeClassifier:
280
285
  scores["infrastructure_layer"] = scores.get("infrastructure_layer", 0) + fan_boost
281
286
 
282
287
  if not scores:
288
+ # IC-004: Java/Maven fallback — no JS signals but module may have a pom.xml
289
+ if not pkg_json:
290
+ return self._classify_maven_module(ws_path, name, fan_in)
283
291
  return "unknown", signals, "low"
284
292
 
285
293
  best_role = max(scores, key=lambda r: (scores[r], _ROLE_PRIORITY.get(r, 0)))
@@ -326,6 +334,76 @@ class RuntimeClassifier:
326
334
  def _criticality(self, role: str, fan_in: int) -> str:
327
335
  if role in {"runtime_core", "plugin_host"}:
328
336
  return "high"
329
- if role in {"backend_runtime", "frontend_runtime", "composition_layer"} or fan_in >= 3:
337
+ # IC-004: infrastructure_layer with high fan_in = shared foundation = high criticality
338
+ if role == "infrastructure_layer" and fan_in >= 3:
339
+ return "high"
340
+ if role in {"backend_runtime", "frontend_runtime", "composition_layer",
341
+ "infrastructure_layer"} or fan_in >= 3:
330
342
  return "medium"
331
343
  return "low"
344
+
345
+ def _compute_maven_fan_in(self, root: Path, workspace_paths: list[str]) -> dict[str, int]:
346
+ """IC-004: Compute cross-module fan_in from all pom.xml files under each workspace.
347
+
348
+ Scans all pom.xml files (top-level and sub-module) within each workspace path.
349
+ Counts distinct modules that declare a dependency containing another module's leaf name.
350
+ Uses 1 hit per source module (not per pom file) to avoid inflation.
351
+ """
352
+ fan_in: dict[str, int] = {}
353
+ leaf_to_path: dict[str, str] = {ws.split("/")[-1]: ws for ws in workspace_paths}
354
+
355
+ for ws_path in workspace_paths:
356
+ ws_dir = root / ws_path
357
+ if not ws_dir.is_dir():
358
+ continue
359
+ deps_found: set[str] = set()
360
+ for pom in ws_dir.rglob("pom.xml"):
361
+ if "target/" in str(pom):
362
+ continue
363
+ try:
364
+ content = pom.read_text(encoding="utf-8", errors="replace")
365
+ except OSError:
366
+ continue
367
+ for dep_leaf, target_path in leaf_to_path.items():
368
+ if target_path != ws_path and dep_leaf in content:
369
+ deps_found.add(target_path)
370
+ for target_path in deps_found:
371
+ fan_in[target_path] = fan_in.get(target_path, 0) + 1
372
+
373
+ return fan_in
374
+
375
+ def _classify_maven_module(
376
+ self, ws_path: str, name: str, fan_in: int
377
+ ) -> tuple[str, list[str], str]:
378
+ """IC-004: Classify a Java/Maven module by path conventions and fan_in.
379
+
380
+ Called when no JS signals exist (no package.json). Uses path name patterns
381
+ and pom fan_in (cross-module dependency count) to determine role/criticality.
382
+ """
383
+ signals: list[str] = ["maven:module"]
384
+ leaf = ws_path.lower().rstrip("/").split("/")[-1]
385
+
386
+ if re.search(r"\b(common|shared|util|utils|lib|base|foundation)\b", leaf):
387
+ signals.append("maven:shared_library")
388
+ return "infrastructure_layer", signals, "high" if fan_in >= 3 else "medium"
389
+
390
+ if re.search(r"\b(integration|it|test|tests|e2e)\b", leaf):
391
+ signals.append("maven:test_module")
392
+ return "test_layer", signals, "low"
393
+
394
+ if re.search(r"\b(admin|web|api|rest|controller|ui)\b", leaf):
395
+ signals.append("maven:web_module")
396
+ return "backend_runtime", signals, "medium"
397
+
398
+ if re.search(r"\b(core|framework|domain|service|profile)\b", leaf):
399
+ signals.append("maven:core_module")
400
+ return "backend_runtime", signals, "high" if fan_in >= 2 else "medium"
401
+
402
+ if fan_in >= 3:
403
+ signals.append(f"maven:high_fan_in({fan_in})")
404
+ return "infrastructure_layer", signals, "high"
405
+ if fan_in >= 1:
406
+ signals.append(f"maven:fan_in({fan_in})")
407
+ return "infrastructure_layer", signals, "medium"
408
+
409
+ return "unknown", signals, "low"
@@ -379,6 +379,59 @@ def _spring_boot_version(sm: "SourceMap") -> "Optional[str]":
379
379
  return None
380
380
 
381
381
 
382
+ def _spring_event_signal(sm: "SourceMap") -> "Optional[dict[str, Any]]":
383
+ """IC-005: Surface @EventListener and publishEvent from Java source files.
384
+
385
+ Scans sm.file_paths for Java files containing Spring event annotations.
386
+ Only runs on Java/Spring projects. Lightweight — path heuristics first,
387
+ then targeted content scan on candidate files only.
388
+ """
389
+ import re as _re
390
+ java_paths = [p for p in sm.file_paths if p.endswith(".java") and "target/" not in p]
391
+ if not java_paths:
392
+ return None
393
+ _frameworks = [f.name for s in (sm.stacks or []) for f in s.frameworks]
394
+ if not any("Spring" in fw for fw in _frameworks):
395
+ return None
396
+
397
+ analyzed_path = getattr(sm.metadata, "analyzed_path", None) if sm.metadata else None
398
+ root = Path(analyzed_path) if analyzed_path else None
399
+
400
+ listeners: list[str] = []
401
+ publishers: list[str] = []
402
+ event_types: set[str] = set()
403
+ _publish_re = _re.compile(r"\.publishEvent\s*\(\s*new\s+(\w+)")
404
+
405
+ for rel_path in java_paths:
406
+ if root is None:
407
+ break
408
+ try:
409
+ content = (root / rel_path).read_text(encoding="utf-8", errors="replace")
410
+ except OSError:
411
+ continue
412
+ if "@EventListener" in content:
413
+ cls_m = _re.search(r"class\s+(\w+)", content)
414
+ cls_name = cls_m.group(1) if cls_m else Path(rel_path).stem
415
+ if cls_name not in listeners:
416
+ listeners.append(cls_name)
417
+ for m in _publish_re.finditer(content):
418
+ event_types.add(m.group(1))
419
+ cls_m = _re.search(r"class\s+(\w+)", content)
420
+ cls_name = cls_m.group(1) if cls_m else Path(rel_path).stem
421
+ if cls_name not in publishers:
422
+ publishers.append(cls_name)
423
+
424
+ if not listeners and not publishers:
425
+ return None
426
+
427
+ return {
428
+ "listeners": sorted(set(listeners))[:10],
429
+ "publishers": sorted(set(publishers))[:10],
430
+ "event_types": sorted(event_types)[:10],
431
+ "flow_count": len(listeners) + len(publishers),
432
+ }
433
+
434
+
382
435
  def _spring_profiles_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
383
436
  """Build structured spring_profiles block: detected names + per-profile file variants."""
384
437
  # Gather profile names from env_summary (populated by env_analyzer scanning
@@ -1949,6 +2002,11 @@ def agent_view(sm: SourceMap, *, full: bool = False) -> dict[str, Any]:
1949
2002
  if _txn:
1950
2003
  signals["transactional_boundaries"] = _txn
1951
2004
 
2005
+ # IC-005: Spring event flows (@EventListener / publishEvent)
2006
+ _evf = _spring_event_signal(sm)
2007
+ if _evf:
2008
+ signals["event_flows"] = _evf
2009
+
1952
2010
  if signals:
1953
2011
  result["signals"] = signals
1954
2012