sourcecode 4.3.0__tar.gz → 4.5.0__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 (213) hide show
  1. {sourcecode-4.3.0 → sourcecode-4.5.0}/CHANGELOG.md +74 -0
  2. {sourcecode-4.3.0 → sourcecode-4.5.0}/PKG-INFO +8 -1
  3. {sourcecode-4.3.0 → sourcecode-4.5.0}/README.md +7 -0
  4. {sourcecode-4.3.0 → sourcecode-4.5.0}/pyproject.toml +1 -1
  5. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/cli.py +14 -0
  7. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/envelope.py +35 -0
  8. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/non_coverage.py +6 -5
  9. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/path_filters.py +13 -0
  10. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/remedies.py +12 -0
  11. sourcecode-4.5.0/src/sourcecode/servlet_surface.py +194 -0
  12. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/test_sources.py +15 -2
  13. {sourcecode-4.3.0 → sourcecode-4.5.0}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-4.3.0 → sourcecode-4.5.0}/.gitignore +0 -0
  15. {sourcecode-4.3.0 → sourcecode-4.5.0}/.ruff.toml +0 -0
  16. {sourcecode-4.3.0 → sourcecode-4.5.0}/CONTRIBUTING.md +0 -0
  17. {sourcecode-4.3.0 → sourcecode-4.5.0}/LICENSE +0 -0
  18. {sourcecode-4.3.0 → sourcecode-4.5.0}/SECURITY.md +0 -0
  19. {sourcecode-4.3.0 → sourcecode-4.5.0}/raw +0 -0
  20. {sourcecode-4.3.0 → sourcecode-4.5.0}/scripts/compare_integration_engines.py +0 -0
  21. {sourcecode-4.3.0 → sourcecode-4.5.0}/scripts/customer_smoke_test.sh +0 -0
  22. {sourcecode-4.3.0 → sourcecode-4.5.0}/scripts/generate_jdk_exports.py +0 -0
  23. {sourcecode-4.3.0 → sourcecode-4.5.0}/scripts/perf_harness.py +0 -0
  24. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/adaptive_scanner.py +0 -0
  25. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/archetype.py +0 -0
  26. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/architectural_baseline.py +0 -0
  27. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/architectural_delta.py +0 -0
  28. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/architecture_analyzer.py +0 -0
  29. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/architecture_summary.py +0 -0
  30. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/ast_extractor.py +0 -0
  31. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/baseline_autocapture.py +0 -0
  32. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/cache.py +0 -0
  33. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/cache_model.py +0 -0
  34. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/call_surface.py +0 -0
  35. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/caller_metrics.py +0 -0
  36. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/caller_reach.py +0 -0
  37. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/canonical_ir.py +0 -0
  38. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/chain_rules.py +0 -0
  39. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/change_plan.py +0 -0
  40. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/cir_graphs.py +0 -0
  41. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/classifier.py +0 -0
  42. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/client_calls.py +0 -0
  43. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  44. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/compare.py +0 -0
  45. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/confidence_analyzer.py +0 -0
  46. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/constraint_diff.py +0 -0
  47. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/context_cache.py +0 -0
  48. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/context_graph.py +0 -0
  49. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/context_scorer.py +0 -0
  50. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/context_summarizer.py +0 -0
  51. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/contract_diff.py +0 -0
  52. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/contract_init.py +0 -0
  53. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/contract_model.py +0 -0
  54. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/contract_pipeline.py +0 -0
  55. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/coverage_parser.py +0 -0
  56. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/data_exposure.py +0 -0
  57. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/data_labels.py +0 -0
  58. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/defect_identity.py +0 -0
  59. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/degradation.py +0 -0
  60. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/dependency_analyzer.py +0 -0
  61. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/deployment_prefix.py +0 -0
  62. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/__init__.py +0 -0
  63. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/base.py +0 -0
  64. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  65. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/dart.py +0 -0
  66. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/dotnet.py +0 -0
  67. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/elixir.py +0 -0
  68. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/go.py +0 -0
  69. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/heuristic.py +0 -0
  70. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/hybrid.py +0 -0
  71. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/java.py +0 -0
  72. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  73. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/nodejs.py +0 -0
  74. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/parsers.py +0 -0
  75. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/php.py +0 -0
  76. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/project.py +0 -0
  77. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/python.py +0 -0
  78. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/ruby.py +0 -0
  79. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/rust.py +0 -0
  80. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/systems.py +0 -0
  81. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/terraform.py +0 -0
  82. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/detectors/tooling.py +0 -0
  83. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/doc_analyzer.py +0 -0
  84. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  85. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/endpoint_literals.py +0 -0
  86. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/endpoint_metrics.py +0 -0
  87. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  88. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/env_analyzer.py +0 -0
  89. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/environment_resolution.py +0 -0
  90. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/error_schema.py +0 -0
  91. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/evidence_provider.py +0 -0
  92. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/explain.py +0 -0
  93. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/facts/__init__.py +0 -0
  94. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/facts/registry.json +0 -0
  95. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/file_chunker.py +0 -0
  96. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/file_classifier.py +0 -0
  97. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/filter_surface.py +0 -0
  98. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/format_contract.py +0 -0
  99. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/fqn_utils.py +0 -0
  100. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/git_analyzer.py +0 -0
  101. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/git_checkout.py +0 -0
  102. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/graph_analyzer.py +0 -0
  103. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/graph_evidence.py +0 -0
  104. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/hibernate_strat.py +0 -0
  105. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/identity_fallback.py +0 -0
  106. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/integration_coordinates.py +0 -0
  107. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/jdk_exports.py +0 -0
  108. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/license.py +0 -0
  109. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/__init__.py +0 -0
  110. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  111. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  112. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  113. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  114. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  115. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  116. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/registry.py +0 -0
  117. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/runner.py +0 -0
  118. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp/server.py +0 -0
  119. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/mcp_nudge.py +0 -0
  120. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/metrics_analyzer.py +0 -0
  121. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/migrate_check.py +0 -0
  122. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/migration_blast.py +0 -0
  123. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/openapi_surface.py +0 -0
  124. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  125. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/output_budget.py +0 -0
  126. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/parse_cache.py +0 -0
  127. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/path_admission.py +0 -0
  128. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/perf.py +0 -0
  129. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/pipe_contract.py +0 -0
  130. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/posture.py +0 -0
  131. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  132. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/pr_impact.py +0 -0
  133. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/prepare_context.py +0 -0
  134. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/progress.py +0 -0
  135. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/ranking_engine.py +0 -0
  136. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/readiness_timeline.py +0 -0
  137. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/reconciliation.py +0 -0
  138. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/redactor.py +0 -0
  139. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/reference_facts.py +0 -0
  140. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/release_info.py +0 -0
  141. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/relevance_scorer.py +0 -0
  142. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/rename_refactor.py +0 -0
  143. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/repo_classifier.py +0 -0
  144. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/repository_ir.py +0 -0
  145. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/__init__.py +0 -0
  146. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/context.py +0 -0
  147. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/errors.py +0 -0
  148. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/executor.py +0 -0
  149. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/planner.py +0 -0
  150. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/query.py +0 -0
  151. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/request.py +0 -0
  152. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/resolution.py +0 -0
  153. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/result.py +0 -0
  154. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/retriever.py +0 -0
  155. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/runtime.py +0 -0
  156. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps.py +0 -0
  157. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  158. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  159. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  160. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  161. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  162. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  163. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/ris.py +0 -0
  164. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/risk.py +0 -0
  165. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/rule_catalog.py +0 -0
  166. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/runtime_classifier.py +0 -0
  167. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/sarif.py +0 -0
  168. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/scanner.py +0 -0
  169. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/schema.py +0 -0
  170. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  171. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/security_config.py +0 -0
  172. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/security_config_scan.py +0 -0
  173. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/security_posture.py +0 -0
  174. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/semantic_analyzer.py +0 -0
  175. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  176. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  177. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/semantic_services.py +0 -0
  178. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/serializer.py +0 -0
  179. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_event_topology.py +0 -0
  180. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_findings.py +0 -0
  181. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_impact.py +0 -0
  182. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_model.py +0 -0
  183. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_profiles.py +0 -0
  184. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_properties.py +0 -0
  185. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_security_audit.py +0 -0
  186. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_semantic.py +0 -0
  187. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  188. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/summarizer.py +0 -0
  189. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/target_admission.py +0 -0
  190. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/__init__.py +0 -0
  191. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/config.py +0 -0
  192. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/consent.py +0 -0
  193. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/events.py +0 -0
  194. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/filters.py +0 -0
  195. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/telemetry/transport.py +0 -0
  196. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/test_gap_ranking.py +0 -0
  197. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/token_estimate.py +0 -0
  198. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/tree_utils.py +0 -0
  199. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/type_usage_surface.py +0 -0
  200. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/validation_inference.py +0 -0
  201. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/validation_surface.py +0 -0
  202. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/verify_edit.py +0 -0
  203. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/verify_repo.py +0 -0
  204. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/verify_rules.py +0 -0
  205. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/version_check.py +0 -0
  206. {sourcecode-4.3.0 → sourcecode-4.5.0}/src/sourcecode/workspace.py +0 -0
  207. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/functions/README.md +0 -0
  208. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/functions/get-license/index.ts +0 -0
  209. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  210. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/functions/telemetry/index.ts +0 -0
  211. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/sql/license_event_ordering.sql +0 -0
  212. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/sql/licensing_schema.sql +0 -0
  213. {sourcecode-4.3.0 → sourcecode-4.5.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,8 +2,82 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.5.0] — 2026-08-03
6
+
7
+ Minor: the servlet-mounted HTTP surface is admitted as a population of its own
8
+ (CL-12's remaining half), the output envelope is published as an open
9
+ specification (M11's first deliverable), and a specification under `docs/` stops
10
+ being counted as a test source. Nothing published is removed and no exit code
11
+ moves; `--servlets` is opt-in and `endpoints.total` is byte-identical with and
12
+ without it.
13
+
14
+ ### Fixed
15
+
16
+ - **A specification under `docs/` was classified as a test source.** `spec/` means a
17
+ test suite in the RSpec/Jasmine sense and the opposite under a documentation root,
18
+ where a *specification* lives — and the file that exposed it is this release's own
19
+ `docs/spec/envelope-v1.md`, which landed in the test population of the tool's own
20
+ metrics the moment it was published. Fixed in the authority (`test_sources`) and in
21
+ the shared helper (`path_filters`); the segment keeps its meaning everywhere else, and
22
+ `docs/tests/` is still tests. Found by the suite, not by the field.
23
+
24
+ ### Added
25
+
26
+ - **`envelope-v1` published as an open specification** (`docs/spec/envelope-v1.md`,
27
+ roadmap M11). Audit #8 named the output envelope the product's most underrated
28
+ strategic asset, and the argument is asymmetric: a format that makes `basis`,
29
+ `limitations`, `unit` and `denominator` mandatory is one an incumbent cannot adopt
30
+ without confessing what its own output leaves out. The document specifies the four
31
+ properties an answer must have — provenance, a named unit, a stated basis with
32
+ `unknown` never spelled `0`, and declared non-coverage — plus the compatibility
33
+ promise (within `envelope_version: 1`, fields are only ever added). **The field table
34
+ is generated from the same JSON Schema `ask schema envelope-v1` prints**, and the
35
+ battery fails when the two disagree: a specification that has drifted from the
36
+ implementation is worth less than none, because the people asked to adopt it check.
37
+
38
+ ### Added
39
+
40
+ - **`ask endpoints --servlets` — the admission half of CL-12 / NC-007.** 4.2.0 declared
41
+ that the endpoint population is Spring handler mappings and that a servlet mapped in
42
+ `web.xml` or registered as a `ServletRegistrationBean` is reachable HTTP surface in
43
+ none of those counts; this lists it, as a population of its own with the URL pattern,
44
+ the servlet class and the declaring file. **It is never merged into
45
+ `endpoints.total`** — the two count different things, and widening a published figure
46
+ moves every ratio keyed on it. Measured: openmrs-core **7 patterns against 1 Spring
47
+ endpoint**, alfresco-community-repo **11**, including `/api/*` served by a WebScript
48
+ servlet. The flag is what NC-007 now names, through the remedy registry, so a rename
49
+ breaks the build rather than the reader. Nothing here resolves what guards those
50
+ patterns and the block says so.
51
+
52
+ ## [4.4.0] — 2026-08-03
53
+
54
+ Minor: M10.5 closes except for the one item blocked on a population nobody in the
55
+ battery has. Two additions, both additive — a new experimental command
56
+ (`ask migrate-recipe`) and a new opt-in block on `endpoints` (`--client-usage`).
57
+ No published field is removed, no default moves, and neither runs unless asked
58
+ for.
59
+
5
60
  ### Added
6
61
 
62
+ - **`ask endpoints --client-usage` — which of these routes anything in this repository
63
+ actually calls** (roadmap M10.5 item 4). On a large surface the cheapest reduction of
64
+ exposure is deleting API nobody calls, and in a monorepo the caller is checked in
65
+ beside the callee. The join reads the TypeScript/JavaScript client, extracts the calls
66
+ whose verb and path are written at the call site, neutralises variable segments on both
67
+ sides (`${bankId}` and `{bank-id}` are one position with two naming conventions) and
68
+ reports three counts: routes called here, routes nothing here calls, and **client calls
69
+ matching no route** — the decisive direction, a request this repository's own front end
70
+ makes and its server does not serve. Measured on open-banking-gateway: 268 client files,
71
+ 31 calls, **30 of 39 routes called, 9 not, 11 calls matching nothing**.
72
+ - It is a **floor** and says so (`direction: under_reports`, new row `NC-009`): a route
73
+ with no call site here is a candidate for deletion, never dead API, because nothing in
74
+ a repository can observe the consumers it does not contain.
75
+ - A URL built at run time is published under `unresolved_call_sites` with its file and
76
+ line, never passed over — it is precisely the call that would make a live route look
77
+ dead, and a call the extractor could not even *see* would shrink the census silently.
78
+ - `--client-dir` points at the client when it is not the whole repository. The join runs
79
+ over the population the filters chose, for the same reason the exposure census does.
80
+
7
81
  - **`ask migrate-recipe` — the migration report in the form the executor takes**
8
82
  (roadmap M10.5 item 5 / M8.5, experimental). `migrate-check` already names the
9
83
  OpenRewrite recipe each finding carries (296 of 309 on openmrs-core) and a reader
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.3.0
3
+ Version: 4.5.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -387,6 +387,12 @@ And one about who calls a route:
387
387
  |---|---|---|
388
388
  | **Whether a route is called from outside this repository — another service, a mobile app, a partner, a cron job, or a client whose URL is assembled at run time.** | The join reads the TypeScript/JavaScript checked in beside the server and only the call shapes whose verb and path are written at the call site. That is a floor on who calls a route and can never be a ceiling: the repository cannot observe consumers it does not contain. So a route with no call site here is published as a candidate for deletion, never as dead API. | Access logs or an API gateway answer who really calls a route. The opposite direction is decisive without them: a client call matching no route is a request this repository's own front end makes and its server does not serve. |
389
389
 
390
+ The one row above with a remedy in this build: `ask endpoints /path/to/repo --servlets`
391
+ lists the servlet-mounted HTTP surface as a population of its own — `web.xml` mappings and
392
+ `ServletRegistrationBean` registrations, with the file that declares each — and never merges
393
+ it into `endpoints.total`. Measured: openmrs-core 7 patterns, alfresco-community-repo 11,
394
+ including `/api/*` served by a WebScript servlet.
395
+
390
396
  **Positioning.** Until an executor ships, this is the **diagnosis layer**: it measures what
391
397
  must change, what each change reaches, and what a gate should block — and it removes none of
392
398
  it. Field evaluation scored it 7/10 as a report generator and 5.5/10 as a development tool,
@@ -479,3 +485,4 @@ Matching endpoints report `policy: "custom"` and drop out of the `no_security_si
479
485
  | [PRODUCT_IDENTITY.md](docs/PRODUCT_IDENTITY.md) | `ask` (command) vs `sourcecode` (package/alias) |
480
486
  | [privacy.md](docs/privacy.md) | Telemetry and data-handling policy |
481
487
  | [DEFECT-LEDGER.md](docs/DEFECT-LEDGER.md) | Every defect found in the field, its class, and which release closed it — published on purpose |
488
+ | [spec/envelope-v1.md](docs/spec/envelope-v1.md) | The output format as an **open specification** — provenance, a named unit, a stated basis, declared non-coverage. Implementable by tools that are not this one; the field table is generated from the schema `ask schema envelope-v1` prints |
@@ -349,6 +349,12 @@ And one about who calls a route:
349
349
  |---|---|---|
350
350
  | **Whether a route is called from outside this repository — another service, a mobile app, a partner, a cron job, or a client whose URL is assembled at run time.** | The join reads the TypeScript/JavaScript checked in beside the server and only the call shapes whose verb and path are written at the call site. That is a floor on who calls a route and can never be a ceiling: the repository cannot observe consumers it does not contain. So a route with no call site here is published as a candidate for deletion, never as dead API. | Access logs or an API gateway answer who really calls a route. The opposite direction is decisive without them: a client call matching no route is a request this repository's own front end makes and its server does not serve. |
351
351
 
352
+ The one row above with a remedy in this build: `ask endpoints /path/to/repo --servlets`
353
+ lists the servlet-mounted HTTP surface as a population of its own — `web.xml` mappings and
354
+ `ServletRegistrationBean` registrations, with the file that declares each — and never merges
355
+ it into `endpoints.total`. Measured: openmrs-core 7 patterns, alfresco-community-repo 11,
356
+ including `/api/*` served by a WebScript servlet.
357
+
352
358
  **Positioning.** Until an executor ships, this is the **diagnosis layer**: it measures what
353
359
  must change, what each change reaches, and what a gate should block — and it removes none of
354
360
  it. Field evaluation scored it 7/10 as a report generator and 5.5/10 as a development tool,
@@ -441,3 +447,4 @@ Matching endpoints report `policy: "custom"` and drop out of the `no_security_si
441
447
  | [PRODUCT_IDENTITY.md](docs/PRODUCT_IDENTITY.md) | `ask` (command) vs `sourcecode` (package/alias) |
442
448
  | [privacy.md](docs/privacy.md) | Telemetry and data-handling policy |
443
449
  | [DEFECT-LEDGER.md](docs/DEFECT-LEDGER.md) | Every defect found in the field, its class, and which release closed it — published on purpose |
450
+ | [spec/envelope-v1.md](docs/spec/envelope-v1.md) | The output format as an **open specification** — provenance, a named unit, a stated basis, declared non-coverage. Implementable by tools that are not this one; the field table is generated from the schema `ask schema envelope-v1` prints |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.3.0"
7
+ version = "4.5.0"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "4.3.0"
7
+ __version__ = "4.5.0"
@@ -4885,6 +4885,12 @@ def endpoints_cmd(
4885
4885
  False, "--by-controller",
4886
4886
  help="Group endpoints by controller class (structured API surface for C4/Container synthesis).",
4887
4887
  ),
4888
+ servlets: bool = typer.Option(
4889
+ False, "--servlets",
4890
+ help="Also list the HTTP surface mounted as a servlet — `web.xml` mappings "
4891
+ "and ServletRegistrationBean registrations (NC-007). Published as a "
4892
+ "population of its own; never merged into the endpoint count.",
4893
+ ),
4888
4894
  client_usage: bool = typer.Option(
4889
4895
  False, "--client-usage",
4890
4896
  help="Join the routes against the HTTP calls written in the TypeScript/"
@@ -5001,6 +5007,14 @@ def endpoints_cmd(
5001
5007
  # exists to remove, one line into its own implementation (R5-R8).
5002
5008
  data["exposure"] = _endpoint_exposure_summary(_selected)
5003
5009
 
5010
+ # CL-12 admission half. NC-007 declared this population in 4.2.0; this lists it,
5011
+ # under its own name and its own total, because the two things counted are not the
5012
+ # same kind of thing and `endpoints.total` is a published figure.
5013
+ if servlets:
5014
+ from sourcecode.servlet_surface import build_servlet_surface
5015
+
5016
+ data["servlet_surface"] = build_servlet_surface(target)
5017
+
5004
5018
  # M10.5 item 4. On a large surface the cheapest reduction of exposure is
5005
5019
  # deleting API nobody calls, and in a monorepo the caller is checked in next
5006
5020
  # to the callee. Joined over the population the filters chose, for the same
@@ -201,3 +201,38 @@ def attach_meta(payload: "dict[str, Any]", meta: "dict[str, Any]") -> "dict[str,
201
201
  else:
202
202
  payload[_META_KEY] = dict(meta)
203
203
  return payload
204
+
205
+
206
+ #: The specification document is *rendered* from the schema, never written beside it.
207
+ #: Audit #8 called the envelope "el activo estratégico más subestimado del producto" and
208
+ #: the asymmetry is the point: a format that makes `basis`, `limitations` and `unit`
209
+ #: mandatory is one an incumbent cannot adopt without confessing. A spec that drifts from
210
+ #: the implementation is worth nothing to the people asked to adopt it, so the document
211
+ #: below is generated from the same JSON Schema `ask schema envelope-v1` prints, and the
212
+ #: battery fails when the published copy no longer matches.
213
+ SPEC_BEGIN = "<!-- BEGIN GENERATED: ask schema envelope-v1 -->"
214
+ SPEC_END = "<!-- END GENERATED -->"
215
+
216
+
217
+ def render_spec_markdown(name: str = "envelope-v1") -> str:
218
+ """The field table of a published schema, as Markdown."""
219
+ schema = load_schema(name)
220
+ required = set(schema.get("required", ()))
221
+ lines = [
222
+ f"**Schema id:** `{schema.get('$id', name)}`",
223
+ "",
224
+ schema.get("description", "").strip(),
225
+ "",
226
+ "| Field | Required | Type | Meaning |",
227
+ "|---|---|---|---|",
228
+ ]
229
+ for field, spec in schema.get("properties", {}).items():
230
+ kind = spec.get("type", "")
231
+ if isinstance(kind, list):
232
+ kind = " \\| ".join(kind)
233
+ meaning = " ".join(str(spec.get("description", "")).split())
234
+ lines.append(
235
+ f"| `{field}` | {'yes' if field in required else 'no'} | "
236
+ f"{kind or '—'} | {meaning} |"
237
+ )
238
+ return "\n".join(lines) + "\n"
@@ -172,11 +172,12 @@ NON_COVERAGE: tuple[NonCoverage, ...] = (
172
172
  "declared before it is widened."
173
173
  ),
174
174
  instead=(
175
- "Read the deployment descriptors for `<servlet-mapping>` entries and "
176
- "the Spring configuration for `ServletRegistrationBean` beans. Field "
177
- "evaluation #9 found a monitoring console mounted this way, behind a "
178
- "credential `ask spring-audit` did report (SEC-007) at a path nothing "
179
- "here listed."
175
+ "`ask endpoints --servlets` lists that surface as a population of its "
176
+ "own `<servlet-mapping>` entries and `ServletRegistrationBean` "
177
+ "registrations, with the declaring file and never merges it into the "
178
+ "endpoint count. Field evaluation #9 found a monitoring console mounted "
179
+ "this way, behind a credential `ask spring-audit` did report (SEC-007) "
180
+ "at a path nothing here listed."
180
181
  ),
181
182
  ),
182
183
  NonCoverage(
@@ -23,6 +23,14 @@ _TEST_MODULE_SEGMENTS = frozenset({
23
23
  "test-support", "testsupport",
24
24
  })
25
25
 
26
+ #: `spec/` means a test suite in the RSpec/Jasmine sense — and it means the opposite
27
+ #: under a documentation root, where a *specification* lives. Found in this repository:
28
+ #: `docs/spec/envelope-v1.md`, the published output-format specification, was classified
29
+ #: as a test file and counted in the test population of the tool's own metrics. The
30
+ #: segment keeps its meaning everywhere else; only these parents disarm it.
31
+ _DOC_ROOT_SEGMENTS = frozenset({"docs", "doc", "documentation"})
32
+ _AMBIGUOUS_TEST_SEGMENTS = frozenset({"spec", "specs"})
33
+
26
34
  _VENDOR_SEGMENTS = frozenset({
27
35
  "vendor", "vendors",
28
36
  "third_party", "thirdparty",
@@ -73,10 +81,15 @@ def is_test_path(path: str) -> bool:
73
81
 
74
82
  # Segment-based check – any directory component is a test segment
75
83
  parts = norm.split("/")
84
+ seen: set[str] = set()
76
85
  for part in parts[:-1]: # skip filename itself
77
86
  bare = part.rstrip("/")
87
+ if bare in _AMBIGUOUS_TEST_SEGMENTS and seen & _DOC_ROOT_SEGMENTS:
88
+ seen.add(bare)
89
+ continue # a specification under `docs/`, not a spec suite
78
90
  if bare in _TEST_SEGMENTS:
79
91
  return True
92
+ seen.add(bare)
80
93
 
81
94
  # File-name conventions
82
95
  name = parts[-1]
@@ -97,6 +97,18 @@ REMEDIES: "dict[str, Remedy]" = {
97
97
  ),
98
98
  headline="derive contracts, write nothing",
99
99
  ),
100
+ Remedy(
101
+ key="http_surface_that_is_not_a_handler_mapping",
102
+ command="endpoints",
103
+ option="--servlets",
104
+ answers=(
105
+ "the endpoint population is Spring handler mappings; to list the HTTP "
106
+ "surface mounted as a servlet instead — `web.xml` mappings and "
107
+ "`ServletRegistrationBean` registrations — as its own population, never "
108
+ "merged into the endpoint count"
109
+ ),
110
+ headline="servlet-mounted HTTP surface",
111
+ ),
100
112
  )
101
113
  }
102
114
 
@@ -0,0 +1,194 @@
1
+ """servlet_surface.py — HTTP surface that is not a Spring handler mapping.
2
+
3
+ Field evaluation #9's second finding was a monitoring console mounted at
4
+ `/panel-de-control` through a `<servlet-mapping>` in `web.xml`, behind a credential
5
+ `spring-audit` did report (SEC-007) at a path **nothing in this product listed**. The
6
+ endpoint population is projected from the Spring route model — one entry per handler
7
+ mapping — and a servlet is reachable over HTTP without being in that model.
8
+
9
+ `NC-007` (4.2.0) declared that gap. This module is its admission half, and the rule
10
+ that shaped it is the reason the declaration came first: **this population is never
11
+ merged into `endpoints.total`**. That count, and every ratio keyed on it, is published
12
+ and read; widening it silently would move numbers a reader has already acted on, and
13
+ the two things counted are not the same kind of thing — a Spring handler has a
14
+ controller, a handler symbol and a security verdict derived from the filter chain; a
15
+ servlet mapping has a URL pattern and a class name, and nothing here resolves what
16
+ guards it.
17
+
18
+ So the answer is a second, named population beside the first, with its own total.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from dataclasses import dataclass
25
+ from pathlib import Path
26
+ from typing import Optional
27
+ from xml.etree import ElementTree as ET
28
+
29
+ SCHEMA_VERSION = "servlet-surface-v1"
30
+
31
+ _SKIP_DIRS = frozenset({".git", "target", "build", "out", "node_modules", "dist"})
32
+
33
+ #: `new ServletRegistrationBean<>(new Foo(), "/path/*")` and the `addUrlMappings` form.
34
+ _REGISTRATION_BEAN = re.compile(
35
+ r"""ServletRegistrationBean\s*(?:<[^>]*>)?\s*\(\s*(?P<args>[^;]{0,400}?)\)\s*;""",
36
+ re.DOTALL,
37
+ )
38
+ _URL_MAPPINGS = re.compile(
39
+ r"""(?:addUrlMappings|setUrlMappings)\s*\(\s*(?P<args>[^;]{0,300}?)\)""", re.DOTALL
40
+ )
41
+ _STRING_LITERAL = re.compile(r"""["']([^"']{1,200})["']""")
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ServletMapping:
46
+ """One servlet reachable over HTTP, and where it was declared."""
47
+
48
+ url_pattern: str
49
+ servlet_name: str
50
+ servlet_class: str
51
+ source_file: str
52
+ source: str # "web.xml" | "servlet_registration_bean"
53
+
54
+ def to_dict(self) -> dict:
55
+ return {
56
+ "url_pattern": self.url_pattern,
57
+ "servlet_name": self.servlet_name,
58
+ "servlet_class": self.servlet_class,
59
+ "source_file": self.source_file,
60
+ "source": self.source,
61
+ }
62
+
63
+
64
+ def _local(tag: str) -> str:
65
+ return tag.rsplit("}", 1)[-1]
66
+
67
+
68
+ def _child_text(elem: "ET.Element", name: str) -> str:
69
+ for child in elem.iter():
70
+ if _local(child.tag) == name and (child.text or "").strip():
71
+ return child.text.strip()
72
+ return ""
73
+
74
+
75
+ def _from_web_xml(xml_path: Path, relative: str) -> "list[ServletMapping]":
76
+ try:
77
+ root = ET.parse(str(xml_path)).getroot()
78
+ except Exception:
79
+ return []
80
+ classes: "dict[str, str]" = {}
81
+ for elem in root.iter():
82
+ if _local(elem.tag) != "servlet":
83
+ continue
84
+ name = _child_text(elem, "servlet-name")
85
+ klass = _child_text(elem, "servlet-class")
86
+ if name:
87
+ classes[name] = klass
88
+ out: "list[ServletMapping]" = []
89
+ for elem in root.iter():
90
+ if _local(elem.tag) != "servlet-mapping":
91
+ continue
92
+ name = _child_text(elem, "servlet-name")
93
+ for child in elem.iter():
94
+ if _local(child.tag) != "url-pattern" or not (child.text or "").strip():
95
+ continue
96
+ out.append(ServletMapping(
97
+ url_pattern=child.text.strip(),
98
+ servlet_name=name,
99
+ servlet_class=classes.get(name, ""),
100
+ source_file=relative,
101
+ source="web.xml",
102
+ ))
103
+ return out
104
+
105
+
106
+ def _from_registration_beans(java_path: Path, relative: str) -> "list[ServletMapping]":
107
+ try:
108
+ text = java_path.read_text(encoding="utf-8", errors="replace")
109
+ except OSError:
110
+ return []
111
+ if "ServletRegistrationBean" not in text:
112
+ return []
113
+ out: "list[ServletMapping]" = []
114
+ for match in _REGISTRATION_BEAN.finditer(text):
115
+ args = match.group("args")
116
+ literals = _STRING_LITERAL.findall(args)
117
+ patterns = [lit for lit in literals if lit.startswith("/")]
118
+ klass = ""
119
+ new_call = re.search(r"new\s+([A-Z][\w.]*)\s*\(", args)
120
+ if new_call:
121
+ klass = new_call.group(1)
122
+ for pattern in patterns:
123
+ out.append(ServletMapping(
124
+ url_pattern=pattern, servlet_name="", servlet_class=klass,
125
+ source_file=relative, source="servlet_registration_bean",
126
+ ))
127
+ for match in _URL_MAPPINGS.finditer(text):
128
+ for literal in _STRING_LITERAL.findall(match.group("args")):
129
+ if literal.startswith("/"):
130
+ out.append(ServletMapping(
131
+ url_pattern=literal, servlet_name="", servlet_class="",
132
+ source_file=relative, source="servlet_registration_bean",
133
+ ))
134
+ return out
135
+
136
+
137
+ def build_servlet_surface(root: Path, *, limit: Optional[int] = None) -> dict:
138
+ """Every servlet-mapped URL pattern under `root`, as a population of its own."""
139
+ from sourcecode import non_coverage
140
+
141
+ root = Path(root)
142
+ mappings: "list[ServletMapping]" = []
143
+ for path in sorted(root.rglob("*")):
144
+ if any(part in _SKIP_DIRS for part in path.parts) or not path.is_file():
145
+ continue
146
+ try:
147
+ relative = str(path.relative_to(root))
148
+ except ValueError:
149
+ relative = str(path)
150
+ if path.name == "web.xml":
151
+ mappings.extend(_from_web_xml(path, relative))
152
+ elif path.suffix == ".java":
153
+ mappings.extend(_from_registration_beans(path, relative))
154
+
155
+ # One URL pattern declared twice is two declarations of one reachable path; the
156
+ # census counts patterns, the rows keep every declaration.
157
+ patterns = sorted({m.url_pattern for m in mappings})
158
+ by_source: "dict[str, int]" = {}
159
+ for mapping in mappings:
160
+ by_source[mapping.source] = by_source.get(mapping.source, 0) + 1
161
+
162
+ rows = [m.to_dict() for m in sorted(
163
+ mappings, key=lambda m: (m.url_pattern, m.source_file)
164
+ )]
165
+ shown = rows if limit is None else rows[:limit]
166
+
167
+ return {
168
+ "schema_version": SCHEMA_VERSION,
169
+ "total_url_patterns": len(patterns),
170
+ "declarations": len(mappings),
171
+ "by_source": dict(sorted(by_source.items())),
172
+ "url_patterns": patterns,
173
+ "mappings": shown,
174
+ "mappings_omitted": len(rows) - len(shown),
175
+ "population": (
176
+ "servlet mappings declared in `web.xml` or registered as a "
177
+ "`ServletRegistrationBean` — HTTP surface that is NOT a Spring handler "
178
+ "mapping and is therefore in none of the endpoint counts"
179
+ ),
180
+ "never_merged": (
181
+ "This total is deliberately separate from `endpoints.total`. The two count "
182
+ "different things — a handler mapping has a controller, a handler symbol "
183
+ "and a security verdict derived from the chain; a servlet mapping has a URL "
184
+ "pattern and a class — and merging them would move a published count and "
185
+ "every ratio keyed on it."
186
+ ),
187
+ "access": (
188
+ "Nothing here resolves what guards these patterns. A servlet is matched by "
189
+ "the same filter chain as everything else, so a filter pattern that covers "
190
+ "it may or may not authenticate it; `ask spring-audit` reports credentials "
191
+ "found in the same descriptors (SEC-007)."
192
+ ),
193
+ "non_coverage": non_coverage.block("endpoints"),
194
+ }
@@ -52,6 +52,14 @@ _TEST_DIR_NAMES: frozenset[str] = frozenset(
52
52
  {"test", "tests", "spec", "specs", "__tests__"}
53
53
  )
54
54
 
55
+ #: `spec/` is a test suite in the RSpec/Jasmine sense — and the opposite under a
56
+ #: documentation root, where a *specification* lives. Found by this project's own suite
57
+ #: when `docs/spec/envelope-v1.md` (the published output-format specification) landed in
58
+ #: the test population of the tool's own metrics. Only these parents disarm the segment;
59
+ #: `spec/` keeps its meaning everywhere else, and `docs/tests/` is still tests.
60
+ _DOC_ROOT_NAMES: frozenset[str] = frozenset({"docs", "doc", "documentation"})
61
+ _AMBIGUOUS_TEST_DIR_NAMES: frozenset[str] = frozenset({"spec", "specs"})
62
+
55
63
  # A directory named `test` under one of these is production code, not a test.
56
64
  _MAIN_ROOT_SEGMENTS: tuple[tuple[str, ...], ...] = (
57
65
  ("src", "main"),
@@ -99,8 +107,13 @@ def declared_test_root(path: str) -> "str | None":
99
107
  # A `test` package inside a main source root is production code.
100
108
  return None
101
109
  for i, part in enumerate(parts[:-1]):
102
- if part in _TEST_DIR_NAMES:
103
- return "/".join(parts[:i + 1])
110
+ if part not in _TEST_DIR_NAMES:
111
+ continue
112
+ if part in _AMBIGUOUS_TEST_DIR_NAMES and (
113
+ set(parts[:i]) & _DOC_ROOT_NAMES
114
+ ):
115
+ continue # a specification under `docs/`, not a spec suite
116
+ return "/".join(parts[:i + 1])
104
117
  return None
105
118
 
106
119
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes