sourcecode 1.30.0__tar.gz → 1.30.2__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 (191) hide show
  1. sourcecode-1.30.2/.continue-here.md +177 -0
  2. {sourcecode-1.30.0 → sourcecode-1.30.2}/PKG-INFO +3 -3
  3. {sourcecode-1.30.0 → sourcecode-1.30.2}/README.md +2 -2
  4. {sourcecode-1.30.0 → sourcecode-1.30.2}/pyproject.toml +1 -1
  5. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/cli.py +2 -0
  7. sourcecode-1.30.2/src/sourcecode/flow_analyzer.py +310 -0
  8. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/prepare_context.py +155 -10
  9. sourcecode-1.30.0/.continue-here.md +0 -118
  10. {sourcecode-1.30.0 → sourcecode-1.30.2}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  11. {sourcecode-1.30.0 → sourcecode-1.30.2}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  12. {sourcecode-1.30.0 → sourcecode-1.30.2}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  13. {sourcecode-1.30.0 → sourcecode-1.30.2}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-1.30.0 → sourcecode-1.30.2}/.gitignore +0 -0
  15. {sourcecode-1.30.0 → sourcecode-1.30.2}/.ruff.toml +0 -0
  16. {sourcecode-1.30.0 → sourcecode-1.30.2}/CONTRIBUTING.md +0 -0
  17. {sourcecode-1.30.0 → sourcecode-1.30.2}/LICENSE +0 -0
  18. {sourcecode-1.30.0 → sourcecode-1.30.2}/SECURITY.md +0 -0
  19. {sourcecode-1.30.0 → sourcecode-1.30.2}/docs/privacy.md +0 -0
  20. {sourcecode-1.30.0 → sourcecode-1.30.2}/docs/schema.md +0 -0
  21. {sourcecode-1.30.0 → sourcecode-1.30.2}/raw +0 -0
  22. {sourcecode-1.30.0 → sourcecode-1.30.2}/run_cli.py +0 -0
  23. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/adaptive_scanner.py +0 -0
  24. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/architecture_analyzer.py +0 -0
  25. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/architecture_summary.py +0 -0
  26. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/ast_extractor.py +0 -0
  27. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/classifier.py +0 -0
  28. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/code_notes_analyzer.py +0 -0
  29. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/confidence_analyzer.py +0 -0
  30. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/context_scorer.py +0 -0
  31. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/context_summarizer.py +0 -0
  32. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/contract_model.py +0 -0
  33. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/contract_pipeline.py +0 -0
  34. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/coverage_parser.py +0 -0
  35. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/dependency_analyzer.py +0 -0
  36. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/__init__.py +0 -0
  37. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/base.py +0 -0
  38. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/csproj_parser.py +0 -0
  39. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/dart.py +0 -0
  40. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/dotnet.py +0 -0
  41. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/elixir.py +0 -0
  42. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/go.py +0 -0
  43. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/heuristic.py +0 -0
  44. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/hybrid.py +0 -0
  45. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/java.py +0 -0
  46. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/jvm_ext.py +0 -0
  47. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/nodejs.py +0 -0
  48. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/parsers.py +0 -0
  49. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/php.py +0 -0
  50. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/project.py +0 -0
  51. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/python.py +0 -0
  52. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/ruby.py +0 -0
  53. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/rust.py +0 -0
  54. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/systems.py +0 -0
  55. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/terraform.py +0 -0
  56. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/detectors/tooling.py +0 -0
  57. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/doc_analyzer.py +0 -0
  58. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/entrypoint_classifier.py +0 -0
  59. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/env_analyzer.py +0 -0
  60. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/file_classifier.py +0 -0
  61. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/git_analyzer.py +0 -0
  62. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/graph_analyzer.py +0 -0
  63. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/metrics_analyzer.py +0 -0
  64. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/progress.py +0 -0
  65. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/ranking_engine.py +0 -0
  66. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/redactor.py +0 -0
  67. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/relevance_scorer.py +0 -0
  68. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/repo_classifier.py +0 -0
  69. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/runtime_classifier.py +0 -0
  70. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/scanner.py +0 -0
  71. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/schema.py +0 -0
  72. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/semantic_analyzer.py +0 -0
  73. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/serializer.py +0 -0
  74. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/summarizer.py +0 -0
  75. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/__init__.py +0 -0
  76. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/config.py +0 -0
  77. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/consent.py +0 -0
  78. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/events.py +0 -0
  79. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/filters.py +0 -0
  80. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/telemetry/transport.py +0 -0
  81. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/tree_utils.py +0 -0
  82. {sourcecode-1.30.0 → sourcecode-1.30.2}/src/sourcecode/workspace.py +0 -0
  83. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/__init__.py +0 -0
  84. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/conftest.py +0 -0
  85. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/coverage.xml +0 -0
  86. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  87. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/fastapi_app/src/main.py +0 -0
  88. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  89. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/go_service/go.mod +0 -0
  90. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/jacoco.xml +0 -0
  91. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/latin1_sample.java +0 -0
  92. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/latin1_sample_iso.java +0 -0
  93. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/lcov.info +0 -0
  94. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  95. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/nextjs_app/package.json +0 -0
  96. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  97. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  98. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  99. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  100. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  101. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  102. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  103. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  104. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  105. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  106. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  107. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  108. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  109. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  110. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  111. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  112. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  113. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  114. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  115. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  116. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  117. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  118. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  119. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  120. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  121. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  122. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  123. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  124. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  125. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  126. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  127. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  128. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  129. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_architecture_analyzer.py +0 -0
  130. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_architecture_summary.py +0 -0
  131. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_ast_extractor.py +0 -0
  132. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_block1_reliability.py +0 -0
  133. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_block2_coverage.py +0 -0
  134. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_block5_quality.py +0 -0
  135. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_bug_fixes_v16.py +0 -0
  136. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_classifier.py +0 -0
  137. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_cli.py +0 -0
  138. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_code_notes_analyzer.py +0 -0
  139. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_context_scorer.py +0 -0
  140. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_contract_pipeline.py +0 -0
  141. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_coverage_parser.py +0 -0
  142. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_cross_consistency.py +0 -0
  143. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_dependency_analyzer_node_python.py +0 -0
  144. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_dependency_analyzer_polyglot.py +0 -0
  145. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_dependency_schema.py +0 -0
  146. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_dotnet.py +0 -0
  147. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_go_rust_java.py +0 -0
  148. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_nodejs.py +0 -0
  149. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_php_ruby_dart.py +0 -0
  150. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_python.py +0 -0
  151. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_universal_managed.py +0 -0
  152. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detector_universal_systems.py +0 -0
  153. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_detectors_base.py +0 -0
  154. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_doc_analyzer_jsdom.py +0 -0
  155. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_doc_analyzer_python.py +0 -0
  156. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_encoding_regression.py +0 -0
  157. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_graph_analyzer_polyglot.py +0 -0
  158. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_graph_analyzer_python_node.py +0 -0
  159. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_graph_schema.py +0 -0
  160. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_hybrid_inference.py +0 -0
  161. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration.py +0 -0
  162. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_dependencies.py +0 -0
  163. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_detection.py +0 -0
  164. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_docs.py +0 -0
  165. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_graph_modules.py +0 -0
  166. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_lqn.py +0 -0
  167. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_metrics.py +0 -0
  168. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_multistack.py +0 -0
  169. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_semantics.py +0 -0
  170. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_integration_universal.py +0 -0
  171. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_java_spring_integration.py +0 -0
  172. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_metrics_analyzer.py +0 -0
  173. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_packaging.py +0 -0
  174. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_phase1_improvements.py +0 -0
  175. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_pipeline_integrity.py +0 -0
  176. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_real_projects.py +0 -0
  177. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_redactor.py +0 -0
  178. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_scanner.py +0 -0
  179. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_schema.py +0 -0
  180. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_schema_normalization.py +0 -0
  181. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_semantic_analyzer_node.py +0 -0
  182. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_semantic_analyzer_python.py +0 -0
  183. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_semantic_import_resolution.py +0 -0
  184. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_semantic_schema.py +0 -0
  185. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_signal_hierarchy.py +0 -0
  186. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_summarizer.py +0 -0
  187. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_surface_honesty.py +0 -0
  188. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_task_differentiation.py +0 -0
  189. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_telemetry.py +0 -0
  190. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_v1_10_regressions.py +0 -0
  191. {sourcecode-1.30.0 → sourcecode-1.30.2}/tests/test_workspace_analyzer.py +0 -0
@@ -0,0 +1,177 @@
1
+ # Continue Here — atlas-cli session 7
2
+
3
+ **Paused:** 2026-05-16 (sesión 7)
4
+ **Repo:** `/Users/user/Downloads/atlas-cli`
5
+ **Branch:** master
6
+ **Último commit:** `7085e97` — (fix) corrigiendo prepare-context review-pr mejorando su representatividad y minimizando falsos positivos
7
+ **Working tree:** LIMPIO
8
+
9
+ ---
10
+
11
+ ## Lo que se hizo esta sesión
12
+
13
+ ### BFS over-expansion fix — 3 fixes semánticos en `prepare_context.py`
14
+
15
+ **Problema de partida** (heredado sesión 6): `review-pr` con 2 archivos cambiados (`User.java`, `ArticleFavorite.java`) devolvía 24 `relevant_files` incluyendo todos los controllers, toda la capa security, GraphQL completo.
16
+
17
+ **Diagnóstico confirmado:**
18
+ - `_classify_changed_file` es path-only, sin inspección de diff → worst-case siempre
19
+ - BFS repo-wide sin constraint de módulo → todo lo que importa `User` incluido
20
+ - Sin change severity classifier → getter renombrado = campo eliminado = mismo BFS
21
+
22
+ ---
23
+
24
+ ### Fix 1 — BFS module constraint
25
+
26
+ Dentro del loop BFS (`_hop_dep_map`), antes de añadir a `_hop_bfs_staged`:
27
+
28
+ ```python
29
+ _is_cross_module = bool(_dep_module) and _dep_module not in affected_modules_set
30
+ if _is_cross_module:
31
+ _seed_expansion = _EXPANSION_TARGETS.get(_seed_atype, frozenset())
32
+ if _seed_severity == "security_change":
33
+ _seed_expansion = _seed_expansion | frozenset({"security", "spring_config", "config"})
34
+ if _hop_num >= 2 or _dep_atype not in _seed_expansion:
35
+ continue
36
+ ```
37
+
38
+ Regla: cross-module solo en hop-1 si dep_atype ∈ `_EXPANSION_TARGETS[seed]`. Hop-2+: cross-module bloqueado siempre.
39
+
40
+ ### Fix 2 — Cross-module score penalty
41
+
42
+ ```python
43
+ _cross_module_factor = 0.60 if _is_cross_module else 1.0
44
+ _dep_score = round(_dep_score_base * (0.70 ** _hop_num) * _cross_module_factor, 2)
45
+ ```
46
+
47
+ Same-module files ganan prioridad en el per-hop cap (8/6/4).
48
+
49
+ ### Fix 3 — `_classify_diff_severity` método nuevo
50
+
51
+ Nuevo método que lee `git diff` y clasifica semánticamente:
52
+ - `trivial` → no siembra BFS (solo comentarios/whitespace)
53
+ - `field_change` → solo hop-1; deps excluidos del frontier hop-2+
54
+ - `api_change` → BFS completo
55
+ - `security_change` → BFS completo + security chain cross-module permitido
56
+ - `unknown` → tratado como api_change (safe default)
57
+
58
+ Integración en `_build_delta_impact`:
59
+ - Step 1b: `diff_severities = {f: self._classify_diff_severity(f, since) for f in changed_files}`
60
+ - Frontier gate: `trivial` files excluidos del BFS frontier
61
+ - Loop gate: `field_change` seeds excluidos del hop-2+ frontier (`_hop_scored` no actualizado)
62
+ - `_seed_severity` computado por seed para aplicar gates correctamente
63
+
64
+ **Bug encontrado durante debug:** `_SECURITY` regex original incluía "password", "role", "permission" — field names comunes en domain models. `this.password = password` en `User.java` disparaba `security_change` falso positivo. Fix: removidos de la regex para Java/TS/Python. Mantenidos: `jwt|auth|token|credential|encrypt|decrypt|oauth|saml|ldap|principal|Security`.
65
+
66
+ **Nota sobre instalación:** `sourcecode` CLI usa pipx venv separado. Para testear source editable:
67
+ ```bash
68
+ /Users/user/.local/pipx/venvs/sourcecode/bin/python -m pip install -e .
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Resultado smoke test (spring-boot-realworld-example-app)
74
+
75
+ ```
76
+ changed_files: User.java, ArticleFavorite.java
77
+ ```
78
+
79
+ | Métrica | Sesión 6 | Sesión 7 |
80
+ |---------|----------|----------|
81
+ | `relevant_files` total | 14 | **4** |
82
+ | Controllers REST | 5 | **0** |
83
+ | Security chain | 3 | **0** |
84
+ | GraphQL | 0 | **0** |
85
+ | Tests | 0 | **0** |
86
+ | Same-module repos | 2 | 2 ✓ |
87
+
88
+ Los 4 archivos resultantes:
89
+ - `User.java` (changed)
90
+ - `ArticleFavorite.java` (changed)
91
+ - `UserRepository.java` (Step 3 same-module expansion)
92
+ - `ArticleFavoriteRepository.java` (Step 3 same-module expansion)
93
+
94
+ ---
95
+
96
+ ## Estado de tests al pausar
97
+
98
+ ```
99
+ 771 passed, 3 skipped
100
+ ```
101
+
102
+ Pre-existing failures (excluidos del run):
103
+ - `test_block2_coverage.py::test_java_marked_unsupported` — DocRecord bug en doc_analyzer.py
104
+ - `test_dependency_analyzer_node_python.py::test_python_requirements_without_lockfile_keeps_declared_versions` — typer version mismatch
105
+ - `test_packaging.py::test_console_script_reports_version` — versión instalada vs source
106
+
107
+ ---
108
+
109
+ ## Decisiones técnicas de esta sesión
110
+
111
+ - **`_EXPANSION_TARGETS` reutilizado en BFS gate**: misma tabla que Step 3, coherente con semántica del sistema
112
+ - **hop-2+ cross-module siempre bloqueado**: excepto security_change que permite security chain
113
+ - **"password" removido de `_SECURITY` en `_classify_diff_severity`**: distingue field name de auth logic real
114
+ - **Cross-module penalty 0.60**: empírico, asegura que same-module gana el per-hop cap
115
+ - **`_bfs_seen.add` antes del gate**: correcto — prevents double-processing aunque archivo sea filtrado
116
+
117
+ ---
118
+
119
+ ## Pendiente / próximos pasos
120
+
121
+ 1. **Smoke test `delta`** — verificar Fix 1-3 no afecta el comando `delta`:
122
+ ```bash
123
+ cd ~/Documents/workspace/spring-boot-realworld-example-app
124
+ sourcecode prepare-context delta . --since HEAD~1 2>/dev/null | python3 -c "
125
+ import json,sys; d=json.load(sys.stdin)
126
+ print('relevant_files:', len(d.get('relevant_files', [])))
127
+ "
128
+ ```
129
+
130
+ 2. **Smoke test `security_change` path** — verificar que cambio real en auth logic SÍ expande security chain:
131
+ - Modificar temporalmente `JwtService.java` (añadir línea con "jwt" o "auth")
132
+ - Correr `review-pr` → debería incluir AuthorizationService, JwtTokenFilter
133
+
134
+ 3. **Tests unitarios para `_classify_diff_severity`** — actualmente sin tests. Casos: trivial (solo comentarios), field_change (field declaration), api_change (import removal), security_change (jwt en diff), unknown (no diff).
135
+
136
+ 4. **`User.java` clasificado como `"source"` no `"domain_model"`** — "user" stem no está en `_ENTITY_KW` ni en `_FOLDER_TYPE_MAP`. Fix opcional: añadir "user", "profile", "account" a detección domain_model.
137
+
138
+ 5. **Smoke tests saint-server** (sesiones anteriores) — pendiente desde sesión 4/5.
139
+
140
+ ---
141
+
142
+ ## Para retomar
143
+
144
+ ```bash
145
+ cd /Users/user/Downloads/atlas-cli
146
+ git log --oneline -3
147
+ # debe mostrar:
148
+ # 7085e97 (fix) corrigiendo prepare-context review-pr mejorando su representatividad...
149
+ # 772a01e (fix) corrigiendo bug que identificaba falsos positivos en review-pr
150
+ # 5c06ef3 (feature) implementando mejoras significativas en prepare-context review-pr
151
+
152
+ # Tests
153
+ python3 -m pytest tests/ \
154
+ --ignore=tests/test_block2_coverage.py \
155
+ --ignore=tests/test_packaging.py \
156
+ --deselect=tests/test_dependency_analyzer_node_python.py::test_python_requirements_without_lockfile_keeps_declared_versions \
157
+ -q
158
+ # Expected: 771 passed, 3 skipped
159
+
160
+ # Reinstall en pipx venv para smoke tests
161
+ /Users/user/.local/pipx/venvs/sourcecode/bin/python -m pip install -e . -q
162
+
163
+ # Smoke test review-pr
164
+ cd ~/Documents/workspace/spring-boot-realworld-example-app
165
+ sourcecode prepare-context review-pr . 2>/dev/null | python3 -c "
166
+ import json, sys
167
+ d = json.load(sys.stdin)
168
+ print('relevant_files:', len(d.get('relevant_files', [])))
169
+ for f in d.get('relevant_files', []):
170
+ print(' ', f.get('score'), f.get('path').split('/')[-1])
171
+ "
172
+ # Expected: 4 files, no controllers, no security, no GraphQL
173
+ ```
174
+
175
+ ---
176
+
177
+ *Pausado 2026-05-16 sesión 7 — gsd:pause-work*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.30.0
3
+ Version: 1.30.2
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
221
221
 
222
222
  **Compressed AI-ready context for Java/Spring enterprise codebases.**
223
223
 
224
- ![Version](https://img.shields.io/badge/version-1.30.0-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.2-blue)
225
225
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
226
226
 
227
227
  ---
@@ -255,7 +255,7 @@ pipx install sourcecode
255
255
 
256
256
  ```bash
257
257
  sourcecode version
258
- # sourcecode 1.30.0
258
+ # sourcecode 1.30.2
259
259
  ```
260
260
 
261
261
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Compressed AI-ready context for Java/Spring enterprise codebases.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.30.0-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.30.2-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -36,7 +36,7 @@ pipx install sourcecode
36
36
 
37
37
  ```bash
38
38
  sourcecode version
39
- # sourcecode 1.30.0
39
+ # sourcecode 1.30.2
40
40
  ```
41
41
 
42
42
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.30.0"
7
+ version = "1.30.2"
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.30.0"
3
+ __version__ = "1.30.2"
@@ -1866,6 +1866,8 @@ def prepare_context_cmd(
1866
1866
  out["review_hotspots"] = output.review_hotspots
1867
1867
  if output.suggested_review_order:
1868
1868
  out["suggested_review_order"] = output.suggested_review_order
1869
+ if output.execution_paths:
1870
+ out["execution_paths"] = output.execution_paths
1869
1871
  if output.impact_summary:
1870
1872
  out["impact_summary"] = output.impact_summary
1871
1873
  if output.why_these_files:
@@ -0,0 +1,310 @@
1
+ """flow_analyzer.py — Evidence-based execution path extraction for PR context.
2
+
3
+ Builds Entry → Service → Repository → EndState ordered sequences using ONLY
4
+ direct code evidence: field injection, constructor params, type annotations,
5
+ method calls, explicit instantiation.
6
+
7
+ V3: execution_paths with runtime_notes — conditional branches, optional execution,
8
+ and async side-effects are surfaced when explicit code signals exist.
9
+ No inference, no naming, no invented behavior.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from pathlib import Path
15
+ from typing import Callable, Optional
16
+
17
+ _ENTRY_ARTIFACT_TYPES = frozenset({"controller", "entrypoint"})
18
+ _SERVICE_ARTIFACT_TYPES = frozenset({"service"})
19
+ _REPO_ARTIFACT_TYPES = frozenset({"repository", "mapper"})
20
+
21
+ _DB_KEYWORDS = frozenset({"repository", "dao", "mapper", "store", "jpa", "jdbc", "sql"})
22
+ _EVENT_KEYWORDS = frozenset({"event", "publish", "emit", "kafka", "queue", "rabbit", "sns", "bus"})
23
+
24
+ _HTTP_ENTRY_RE = re.compile(
25
+ r'@(?:Get|Post|Put|Delete|Patch|Request)Mapping[^)]*\)'
26
+ r'|@(?:Get|Post|Put|Delete|Patch)\([^)]*\)'
27
+ r'|@\w+\.(?:get|post|put|delete|patch)\([^)]*\)',
28
+ re.IGNORECASE,
29
+ )
30
+ _METHOD_NAME_RE = re.compile(
31
+ r'(?:public\s+|async\s+|def\s+|function\s+)*'
32
+ r'(?:[\w<>\[\]]+\s+)?'
33
+ r'(\w+)\s*\(',
34
+ )
35
+
36
+ # Runtime signal patterns: (compiled_regex, note_text)
37
+ # Only signals with explicit code evidence — no inference.
38
+ # Three categories: condition | branch | async
39
+ _RUNTIME_SIGNALS: list[tuple[re.Pattern, str]] = [
40
+ # ── Conditional / auth guards ─────────────────────────────────────────────
41
+ (re.compile(r'@PreAuthorize|@Secured|@RolesAllowed', re.IGNORECASE),
42
+ "condition: authorization check present (@PreAuthorize / @Secured)"),
43
+ (re.compile(r'isAuthenticated\(\)|hasRole\(|hasAuthority\(|SecurityContextHolder', re.IGNORECASE),
44
+ "condition: reads authentication context"),
45
+ (re.compile(r'featureFlag|FeatureToggle|\.isEnabled\s*\(|\.isActive\s*\(', re.IGNORECASE),
46
+ "condition: feature flag gates execution"),
47
+ # Null/empty guard with early return — matches if (...null/empty...) return/throw on same line
48
+ (re.compile(r'if\s*\([^)]*(?:==\s*null|!=\s*null|isEmpty\s*\(\)|isBlank\s*\(\))[^)]*\)'
49
+ r'\s*(?:\{?\s*)?(?:return|throw)\b', re.IGNORECASE),
50
+ "condition: null/empty guard with early return"),
51
+
52
+ # ── Optional execution / branching ────────────────────────────────────────
53
+ (re.compile(r'@Cacheable|@CacheEvict|@CachePut', re.IGNORECASE),
54
+ "branch: Spring cache may short-circuit downstream call"),
55
+ (re.compile(r'\.getIfPresent\s*\(|cache\.get\s*\(|cacheManager\.', re.IGNORECASE),
56
+ "branch: manual cache lookup may short-circuit"),
57
+ (re.compile(r'Optional\s*<|\.orElseThrow\s*\(|\.orElseGet\s*\(|\.orElse\s*\(', re.IGNORECASE),
58
+ "branch: result may be absent (Optional)"),
59
+
60
+ # ── Async / side effects ──────────────────────────────────────────────────
61
+ (re.compile(r'@Async\b'),
62
+ "async: runs in separate thread (@Async)"),
63
+ (re.compile(r'CompletableFuture|\.supplyAsync\s*\(|\.runAsync\s*\('),
64
+ "async: non-blocking future-based execution"),
65
+ (re.compile(r'\basync\s+def\b|\bawait\b', re.IGNORECASE),
66
+ "async: non-blocking (async/await)"),
67
+ (re.compile(r'publishEvent\s*\(|applicationEventPublisher|eventPublisher\.', re.IGNORECASE),
68
+ "async: Spring application event emitted"),
69
+ (re.compile(r'kafkaTemplate\.|KafkaProducer|@KafkaListener', re.IGNORECASE),
70
+ "async: Kafka message produced"),
71
+ (re.compile(r'rabbitTemplate\.|amqpTemplate\.|@RabbitListener', re.IGNORECASE),
72
+ "async: RabbitMQ message sent"),
73
+ ]
74
+
75
+
76
+ def _detect_lang(path: str) -> str:
77
+ return {
78
+ ".java": "java", ".kt": "kotlin",
79
+ ".py": "python",
80
+ ".ts": "typescript", ".tsx": "typescript",
81
+ ".js": "javascript", ".jsx": "javascript",
82
+ ".go": "go", ".cs": "csharp", ".rb": "ruby", ".php": "php",
83
+ }.get(Path(path).suffix.lower(), "unknown")
84
+
85
+
86
+ def _strip_comments(content: str, lang: str) -> str:
87
+ content = re.sub(r"/\*.*?\*/", " ", content, flags=re.DOTALL)
88
+ content = re.sub(r"//[^\n]*", " ", content)
89
+ if lang in ("python", "ruby", "go"):
90
+ content = re.sub(r"#[^\n]*", " ", content)
91
+ return content
92
+
93
+
94
+ def _read_safe(root: Path, rel_path: str) -> str:
95
+ try:
96
+ return (root / rel_path).read_text(encoding="utf-8", errors="ignore")
97
+ except (OSError, ValueError):
98
+ return ""
99
+
100
+
101
+ def _collect_runtime_notes(content: str, lang: str) -> list[str]:
102
+ """Scan comment-stripped content for explicit runtime behavior signals.
103
+
104
+ Returns only notes backed by a direct code pattern match.
105
+ Returns [] when no signals are found.
106
+ """
107
+ clean = _strip_comments(content, lang)
108
+ notes: list[str] = []
109
+ seen: set[str] = set()
110
+ for pattern, note in _RUNTIME_SIGNALS:
111
+ if note not in seen and pattern.search(clean):
112
+ notes.append(note)
113
+ seen.add(note)
114
+ return notes
115
+
116
+
117
+ def _find_entry_method(clean: str) -> Optional[str]:
118
+ m = _HTTP_ENTRY_RE.search(clean)
119
+ if not m:
120
+ return None
121
+ after = clean[m.end():]
122
+ mn = _METHOD_NAME_RE.match(after.lstrip())
123
+ if mn:
124
+ name = mn.group(1)
125
+ if name.lower() not in ("public", "async", "def", "function", "void", "override"):
126
+ return name
127
+ return None
128
+
129
+
130
+ def _build_field_map(clean: str) -> dict[str, str]:
131
+ """Map field_name_lower → ClassName from injection patterns."""
132
+ fmap: dict[str, str] = {}
133
+ for m in re.finditer(r"private\s+(\w+)(?:<[^>]+>)?\s+(\w+)\s*[;=,)]", clean):
134
+ fmap[m.group(2).lower()] = m.group(1)
135
+ for m in re.finditer(r"(?:private|protected|readonly)\s+(\w+)\s*:\s*(\w+)", clean):
136
+ fmap[m.group(1).lower()] = m.group(2)
137
+ for m in re.finditer(r"self\.(\w+)\s*=\s*(\w+)\s*\(", clean):
138
+ fmap[m.group(1).lower()] = m.group(2)
139
+ return fmap
140
+
141
+
142
+ def _find_called_method(clean: str, class_name: str, fmap: dict[str, str]) -> Optional[str]:
143
+ fields = [f for f, t in fmap.items() if t.lower() == class_name.lower()]
144
+ for field in fields:
145
+ pat = rf"\bthis\.{re.escape(field)}\.(\w+)\s*\(|\b{re.escape(field)}\.(\w+)\s*\("
146
+ for m in re.finditer(pat, clean, re.IGNORECASE):
147
+ name = m.group(1) or m.group(2)
148
+ if name and name.lower() not in ("class", "new", "super", "get", "set"):
149
+ return name
150
+ for m in re.finditer(rf"\b{re.escape(class_name)}\.(\w+)\s*\(", clean, re.IGNORECASE):
151
+ name = m.group(1)
152
+ if name.lower() not in ("class", "new", "super"):
153
+ return name
154
+ return None
155
+
156
+
157
+ def _has_code_evidence(clean: str, class_name: str) -> bool:
158
+ """True only when class_name has direct code evidence in pre-stripped content."""
159
+ esc = re.escape(class_name)
160
+ if re.search(rf"\b(?:private|protected)\s+{esc}\b", clean, re.IGNORECASE):
161
+ return True
162
+ if re.search(rf"[,(]\s*{esc}\s+\w+", clean, re.IGNORECASE):
163
+ return True
164
+ if re.search(rf":\s*{esc}\b", clean, re.IGNORECASE):
165
+ return True
166
+ if re.search(rf"\bnew\s+{esc}\s*\(", clean, re.IGNORECASE):
167
+ return True
168
+ if re.search(rf"\b{esc}\s*\(", clean):
169
+ return True
170
+ if re.search(rf"\b{esc}\b", clean, re.IGNORECASE):
171
+ non_import = re.search(
172
+ rf"^(?!\s*(?:import|require|from|//|#|\*)\b).*\b{esc}\b",
173
+ clean, re.IGNORECASE | re.MULTILINE,
174
+ )
175
+ if non_import:
176
+ return True
177
+ return False
178
+
179
+
180
+ def _find_evidenced_ordered(
181
+ root: Path,
182
+ source_path: str,
183
+ candidates: list[str],
184
+ ) -> list[tuple[str, Optional[str]]]:
185
+ """Return (class_name, method_or_None) for candidates with direct code evidence,
186
+ ordered by their first appearance position in the source file."""
187
+ content = _read_safe(root, source_path)
188
+ if not content:
189
+ return []
190
+ lang = _detect_lang(source_path)
191
+ clean = _strip_comments(content, lang)
192
+ fmap = _build_field_map(clean)
193
+
194
+ positioned: list[tuple[int, str, Optional[str]]] = []
195
+ for cand_path in candidates:
196
+ class_name = Path(cand_path).stem
197
+ if not _has_code_evidence(clean, class_name):
198
+ continue
199
+ method = _find_called_method(clean, class_name, fmap)
200
+ m = re.search(rf"\b{re.escape(class_name)}\b", clean, re.IGNORECASE)
201
+ pos = m.start() if m else len(clean)
202
+ positioned.append((pos, class_name, method))
203
+
204
+ positioned.sort(key=lambda x: x[0])
205
+ return [(cls, meth) for _, cls, meth in positioned]
206
+
207
+
208
+ def _detect_end_state(path: list[str]) -> str:
209
+ for step in path:
210
+ s = step.lower()
211
+ if any(kw in s for kw in _DB_KEYWORDS):
212
+ return "DB write"
213
+ if any(kw in s for kw in _EVENT_KEYWORDS):
214
+ return "event emitted"
215
+ return "HTTP response"
216
+
217
+
218
+ def _step_label(class_name: str, method: Optional[str]) -> str:
219
+ return f"{class_name}.{method}" if method else class_name
220
+
221
+
222
+ def _path_name(entry_class: str) -> str:
223
+ domain = re.sub(
224
+ r"(?:RestController|Controller|Resource|Handler|Api|Endpoint|Router|Servlet)$",
225
+ "", entry_class, flags=re.IGNORECASE,
226
+ )
227
+ return re.sub(r"(?<=[a-z])(?=[A-Z])", " ", domain).strip()
228
+
229
+
230
+ def analyze_execution_paths(
231
+ changed_files: list[str],
232
+ all_paths: list[str],
233
+ root: Path,
234
+ classify_fn: Callable[[str], dict],
235
+ max_paths: int = 3,
236
+ ) -> list[dict]:
237
+ """Build ordered execution paths with runtime behavior signals.
238
+
239
+ Each path:
240
+ - One service per entry point (most evident, earliest-referenced)
241
+ - Each step requires direct code evidence
242
+ - runtime_notes populated from explicit code signals only (never inferred)
243
+ - Forward-only: Controller → Service → Repository
244
+
245
+ Returns list of: {name, entry_point, path, runtime_notes, end_state}
246
+ Returns [] when no verifiable path exists.
247
+ """
248
+ entry_files = [
249
+ f for f in changed_files
250
+ if classify_fn(f)["artifact_type"] in _ENTRY_ARTIFACT_TYPES
251
+ ]
252
+ if not entry_files:
253
+ return []
254
+
255
+ all_services = [p for p in all_paths if classify_fn(p)["artifact_type"] in _SERVICE_ARTIFACT_TYPES]
256
+ all_repos = [p for p in all_paths if classify_fn(p)["artifact_type"] in _REPO_ARTIFACT_TYPES]
257
+
258
+ result: list[dict] = []
259
+
260
+ for entry_path in entry_files[:max_paths]:
261
+ entry_class = Path(entry_path).stem
262
+ lang = _detect_lang(entry_path)
263
+
264
+ entry_content = _read_safe(root, entry_path)
265
+ entry_clean = _strip_comments(entry_content, lang) if entry_content else ""
266
+ entry_method = _find_entry_method(entry_clean) if entry_clean else None
267
+ entry_point_str = _step_label(entry_class, entry_method)
268
+
269
+ evidenced_svcs = _find_evidenced_ordered(root, entry_path, all_services)
270
+ if not evidenced_svcs:
271
+ continue
272
+
273
+ svc_class, svc_method = evidenced_svcs[0]
274
+ svc_label = _step_label(svc_class, svc_method)
275
+
276
+ svc_path = next((p for p in all_services if Path(p).stem == svc_class), None)
277
+ svc_content = _read_safe(root, svc_path) if svc_path else ""
278
+ svc_lang = _detect_lang(svc_path) if svc_path else "unknown"
279
+
280
+ # Service step — notes scoped to service file only
281
+ path_items: list[dict] = [
282
+ {"step": svc_label,
283
+ "notes": _collect_runtime_notes(svc_content, svc_lang) if svc_content else []},
284
+ ]
285
+
286
+ # Repository step — notes scoped to repo file only
287
+ if svc_path:
288
+ evidenced_repos = _find_evidenced_ordered(root, svc_path, all_repos)
289
+ if evidenced_repos:
290
+ repo_class, repo_method = evidenced_repos[0]
291
+ repo_label = _step_label(repo_class, repo_method)
292
+ repo_path = next((p for p in all_repos if Path(p).stem == repo_class), None)
293
+ repo_content = _read_safe(root, repo_path) if repo_path else ""
294
+ repo_lang = _detect_lang(repo_path) if repo_path else "unknown"
295
+ path_items.append(
296
+ {"step": repo_label,
297
+ "notes": _collect_runtime_notes(repo_content, repo_lang) if repo_content else []},
298
+ )
299
+
300
+ # Entry-point notes scoped to controller file
301
+ entry_notes = _collect_runtime_notes(entry_content, lang) if entry_content else []
302
+
303
+ result.append({
304
+ "name": _path_name(entry_class),
305
+ "entry_point": {"step": entry_point_str, "notes": entry_notes},
306
+ "path": path_items,
307
+ "end_state": _detect_end_state([item["step"] for item in path_items]),
308
+ })
309
+
310
+ return result