sourcecode 4.6.0__tar.gz → 4.7.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 (215) hide show
  1. {sourcecode-4.6.0 → sourcecode-4.7.0}/CHANGELOG.md +22 -0
  2. {sourcecode-4.6.0 → sourcecode-4.7.0}/PKG-INFO +4 -4
  3. {sourcecode-4.6.0 → sourcecode-4.7.0}/README.md +3 -3
  4. {sourcecode-4.6.0 → sourcecode-4.7.0}/pyproject.toml +1 -1
  5. {sourcecode-4.6.0 → sourcecode-4.7.0}/scripts/perf_harness.py +38 -1
  6. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/__init__.py +1 -1
  7. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/cli.py +15 -3
  8. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/java.py +4 -0
  9. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/registry.py +9 -4
  10. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/server.py +13 -4
  11. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/migrate_check.py +24 -1
  12. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/perf.py +112 -1
  13. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/rule_catalog.py +10 -3
  14. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/security_config_scan.py +172 -0
  15. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/source_text.py +2 -3
  16. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_impact.py +57 -21
  17. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_security_audit.py +20 -2
  18. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/verify_rules.py +1 -1
  19. {sourcecode-4.6.0 → sourcecode-4.7.0}/.github/workflows/build-windows.yml +0 -0
  20. {sourcecode-4.6.0 → sourcecode-4.7.0}/.gitignore +0 -0
  21. {sourcecode-4.6.0 → sourcecode-4.7.0}/.ruff.toml +0 -0
  22. {sourcecode-4.6.0 → sourcecode-4.7.0}/CLAUDE.md +0 -0
  23. {sourcecode-4.6.0 → sourcecode-4.7.0}/CONTRIBUTING.md +0 -0
  24. {sourcecode-4.6.0 → sourcecode-4.7.0}/LICENSE +0 -0
  25. {sourcecode-4.6.0 → sourcecode-4.7.0}/SECURITY.md +0 -0
  26. {sourcecode-4.6.0 → sourcecode-4.7.0}/raw +0 -0
  27. {sourcecode-4.6.0 → sourcecode-4.7.0}/scripts/compare_integration_engines.py +0 -0
  28. {sourcecode-4.6.0 → sourcecode-4.7.0}/scripts/customer_smoke_test.sh +0 -0
  29. {sourcecode-4.6.0 → sourcecode-4.7.0}/scripts/generate_jdk_exports.py +0 -0
  30. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/adaptive_scanner.py +0 -0
  31. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/archetype.py +0 -0
  32. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/architectural_baseline.py +0 -0
  33. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/architectural_delta.py +0 -0
  34. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/architecture_analyzer.py +0 -0
  35. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/architecture_summary.py +0 -0
  36. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/ast_extractor.py +0 -0
  37. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/baseline_autocapture.py +0 -0
  38. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/cache.py +0 -0
  39. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/cache_model.py +0 -0
  40. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/call_surface.py +0 -0
  41. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/caller_metrics.py +0 -0
  42. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/caller_reach.py +0 -0
  43. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/canonical_ir.py +0 -0
  44. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/chain_rules.py +0 -0
  45. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/change_plan.py +0 -0
  46. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/cir_graphs.py +0 -0
  47. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/classifier.py +0 -0
  48. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/client_calls.py +0 -0
  49. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  50. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/compare.py +0 -0
  51. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/confidence_analyzer.py +0 -0
  52. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/constraint_diff.py +0 -0
  53. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/context_cache.py +0 -0
  54. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/context_graph.py +0 -0
  55. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/context_scorer.py +0 -0
  56. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/context_summarizer.py +0 -0
  57. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/contract_diff.py +0 -0
  58. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/contract_init.py +0 -0
  59. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/contract_model.py +0 -0
  60. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/contract_pipeline.py +0 -0
  61. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/coverage_parser.py +0 -0
  62. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/data_exposure.py +0 -0
  63. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/data_labels.py +0 -0
  64. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/defect_identity.py +0 -0
  65. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/degradation.py +0 -0
  66. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/dependency_analyzer.py +0 -0
  67. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/deployment_prefix.py +0 -0
  68. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/__init__.py +0 -0
  69. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/base.py +0 -0
  70. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  71. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/dart.py +0 -0
  72. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/dotnet.py +0 -0
  73. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/elixir.py +0 -0
  74. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/go.py +0 -0
  75. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/heuristic.py +0 -0
  76. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/hybrid.py +0 -0
  77. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  78. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/nodejs.py +0 -0
  79. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/parsers.py +0 -0
  80. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/php.py +0 -0
  81. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/project.py +0 -0
  82. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/python.py +0 -0
  83. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/ruby.py +0 -0
  84. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/rust.py +0 -0
  85. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/systems.py +0 -0
  86. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/terraform.py +0 -0
  87. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/detectors/tooling.py +0 -0
  88. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/doc_analyzer.py +0 -0
  89. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  90. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/endpoint_literals.py +0 -0
  91. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/endpoint_metrics.py +0 -0
  92. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  93. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/env_analyzer.py +0 -0
  94. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/envelope.py +0 -0
  95. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/environment_resolution.py +0 -0
  96. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/error_schema.py +0 -0
  97. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/evidence_provider.py +0 -0
  98. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/explain.py +0 -0
  99. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/facts/__init__.py +0 -0
  100. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/facts/registry.json +0 -0
  101. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/file_chunker.py +0 -0
  102. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/file_classifier.py +0 -0
  103. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/filter_surface.py +0 -0
  104. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/format_contract.py +0 -0
  105. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/fqn_utils.py +0 -0
  106. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/git_analyzer.py +0 -0
  107. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/git_checkout.py +0 -0
  108. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/graph_analyzer.py +0 -0
  109. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/graph_evidence.py +0 -0
  110. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/hibernate_strat.py +0 -0
  111. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/identity_fallback.py +0 -0
  112. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/integration_coordinates.py +0 -0
  113. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/jdk_exports.py +0 -0
  114. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/license.py +0 -0
  115. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/__init__.py +0 -0
  116. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  117. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  118. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  119. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  120. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  121. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  122. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp/runner.py +0 -0
  123. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/mcp_nudge.py +0 -0
  124. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/metrics_analyzer.py +0 -0
  125. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/migration_blast.py +0 -0
  126. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/non_coverage.py +0 -0
  127. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/openapi_surface.py +0 -0
  128. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  129. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/output_budget.py +0 -0
  130. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/parse_cache.py +0 -0
  131. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/path_admission.py +0 -0
  132. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/path_filters.py +0 -0
  133. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/pipe_contract.py +0 -0
  134. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/posture.py +0 -0
  135. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  136. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/pr_impact.py +0 -0
  137. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/prepare_context.py +0 -0
  138. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/progress.py +0 -0
  139. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/ranking_engine.py +0 -0
  140. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/readiness_timeline.py +0 -0
  141. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/reconciliation.py +0 -0
  142. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/redactor.py +0 -0
  143. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/reference_facts.py +0 -0
  144. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/release_info.py +0 -0
  145. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/relevance_scorer.py +0 -0
  146. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/remedies.py +0 -0
  147. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/rename_refactor.py +0 -0
  148. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/repo_classifier.py +0 -0
  149. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/repository_ir.py +0 -0
  150. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/__init__.py +0 -0
  151. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/context.py +0 -0
  152. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/errors.py +0 -0
  153. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/executor.py +0 -0
  154. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/planner.py +0 -0
  155. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/query.py +0 -0
  156. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/request.py +0 -0
  157. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/resolution.py +0 -0
  158. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/result.py +0 -0
  159. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/retriever.py +0 -0
  160. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/runtime.py +0 -0
  161. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps.py +0 -0
  162. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  163. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  164. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  165. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  166. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  167. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  168. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/ris.py +0 -0
  169. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/risk.py +0 -0
  170. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/runtime_classifier.py +0 -0
  171. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/sarif.py +0 -0
  172. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/scanner.py +0 -0
  173. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/schema.py +0 -0
  174. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  175. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/security_config.py +0 -0
  176. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/security_posture.py +0 -0
  177. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/semantic_analyzer.py +0 -0
  178. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  179. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  180. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/semantic_services.py +0 -0
  181. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/serializer.py +0 -0
  182. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/servlet_surface.py +0 -0
  183. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_event_topology.py +0 -0
  184. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_findings.py +0 -0
  185. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_model.py +0 -0
  186. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_profiles.py +0 -0
  187. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_properties.py +0 -0
  188. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_semantic.py +0 -0
  189. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  190. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/summarizer.py +0 -0
  191. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/target_admission.py +0 -0
  192. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/__init__.py +0 -0
  193. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/config.py +0 -0
  194. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/consent.py +0 -0
  195. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/events.py +0 -0
  196. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/filters.py +0 -0
  197. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/telemetry/transport.py +0 -0
  198. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/test_gap_ranking.py +0 -0
  199. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/test_sources.py +0 -0
  200. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/token_estimate.py +0 -0
  201. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/tree_utils.py +0 -0
  202. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/type_usage_surface.py +0 -0
  203. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/validation_inference.py +0 -0
  204. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/validation_surface.py +0 -0
  205. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/verify_edit.py +0 -0
  206. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/verify_repo.py +0 -0
  207. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/version_check.py +0 -0
  208. {sourcecode-4.6.0 → sourcecode-4.7.0}/src/sourcecode/workspace.py +0 -0
  209. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/functions/README.md +0 -0
  210. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/functions/get-license/index.ts +0 -0
  211. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  212. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/functions/telemetry/index.ts +0 -0
  213. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/sql/license_event_ordering.sql +0 -0
  214. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/sql/licensing_schema.sql +0 -0
  215. {sourcecode-4.6.0 → sourcecode-4.7.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.7.0] — 2026-08-04
6
+
7
+ ### Fixed
8
+
9
+ - **`impact-chain` no longer pays a repository-wide TX + security audit by default.**
10
+ `impact_findings` is now opt-in with `--with-findings`, so the common query cost
11
+ follows the queried subgraph instead of the whole repository.
12
+ - **`migrate-check` now separates migration blockers from broad blockers.**
13
+ The payload publishes `migration_blocking_count` while keeping the legacy
14
+ `blocking_count` for compatibility.
15
+ - **Disabled security controls are reported explicitly.** Commented-out
16
+ framework security annotations now emit `DEAD-001`, and security catalog/help
17
+ output includes disabled-control findings.
18
+ - **MyBatis SQL interpolation is classified as SQL-injection risk.** Mapper
19
+ `${...}` interpolation emits `SEC-008` instead of being treated as a generic
20
+ raw SQL smell.
21
+ - **Performance claims now have endpoint-scale coverage.** The battery includes
22
+ a high-endpoint fixture, publishes scale dimensions, and gates unexplained
23
+ performance regressions against prior measurements.
24
+ - **Framework detection covers Jersey and Governator.** Migration/readiness
25
+ output no longer misses those stacks when scanning mixed Java repositories.
26
+
5
27
  ## [4.6.0] — 2026-08-04
6
28
 
7
29
  Field evaluation #11 (7,5/10), by the evaluator who measured 4.5.2 twelve hours
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.6.0
3
+ Version: 4.7.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
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-4.6.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.7.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -97,7 +97,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
97
97
  # pip / pipx
98
98
  pipx install sourcecode # or: pip install sourcecode
99
99
 
100
- ask version # ask 4.6.0 — and, on a build that has aged,
100
+ ask version # ask 4.7.0 — and, on a build that has aged,
101
101
  # how many releases have probably shipped since
102
102
  ```
103
103
 
@@ -211,7 +211,7 @@ Bounded, noise-free repo context designed to drop straight into an agent's conte
211
211
 
212
212
  ### 2 · Impact Analysis
213
213
  Blast radius from a class or interface — reverse dependencies, through Spring DI, to the HTTP endpoints a change reaches.
214
- `ask impact` · `ask impact-chain` (TX/SEC-enriched) · `ask pr-impact`
214
+ `ask impact` · `ask impact-chain` (blast radius; `--with-findings` for TX/SEC audit findings) · `ask pr-impact`
215
215
  → [reference](docs/USER_GUIDE.md#core-commands)
216
216
 
217
217
  ### 3 · Architecture Intelligence
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
6
6
 
7
- ![Version](https://img.shields.io/badge/version-4.6.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-4.7.0-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -59,7 +59,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
59
59
  # pip / pipx
60
60
  pipx install sourcecode # or: pip install sourcecode
61
61
 
62
- ask version # ask 4.6.0 — and, on a build that has aged,
62
+ ask version # ask 4.7.0 — and, on a build that has aged,
63
63
  # how many releases have probably shipped since
64
64
  ```
65
65
 
@@ -173,7 +173,7 @@ Bounded, noise-free repo context designed to drop straight into an agent's conte
173
173
 
174
174
  ### 2 · Impact Analysis
175
175
  Blast radius from a class or interface — reverse dependencies, through Spring DI, to the HTTP endpoints a change reaches.
176
- `ask impact` · `ask impact-chain` (TX/SEC-enriched) · `ask pr-impact`
176
+ `ask impact` · `ask impact-chain` (blast radius; `--with-findings` for TX/SEC audit findings) · `ask pr-impact`
177
177
  → [reference](docs/USER_GUIDE.md#core-commands)
178
178
 
179
179
  ### 3 · Architecture Intelligence
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.6.0"
7
+ version = "4.7.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"
@@ -201,6 +201,12 @@ def main() -> int:
201
201
  ap.add_argument("--fleet-root", type=Path, default=DEFAULT_FLEET_ROOT)
202
202
  ap.add_argument("--out", type=Path, default=None,
203
203
  help="Output dir (default: docs/perf/baselines/<tool_version>).")
204
+ ap.add_argument("--compare-dir", type=Path, default=None,
205
+ help="Previous perf-baseline directory to gate against.")
206
+ ap.add_argument("--max-regression", type=float, default=0.25,
207
+ help="Allowed wall p50/p95 regression ratio before failing (default: 0.25).")
208
+ ap.add_argument("--allow-incomparable", action="store_true",
209
+ help="Do not fail when baseline/current cells are incomparable.")
204
210
  ap.add_argument("--quick", action="store_true", help="petclinic only, 2 runs — smoke test.")
205
211
  ap.add_argument("--dry-run", action="store_true", help="Print the plan, run nothing.")
206
212
  args = ap.parse_args()
@@ -227,6 +233,7 @@ def main() -> int:
227
233
  return 0
228
234
 
229
235
  out_dir.mkdir(parents=True, exist_ok=True)
236
+ failures: list[tuple[str, dict]] = []
230
237
  for cmd, name, mode in plan:
231
238
  subdir = FLEET.get(name, name)
232
239
  repo_path = args.fleet_root / subdir
@@ -242,9 +249,39 @@ def main() -> int:
242
249
  fname = f"{name}__{cmd or 'ask'}__{mode}.json"
243
250
  (out_dir / fname).write_text(json.dumps(cell, indent=2) + "\n", encoding="utf-8")
244
251
  w = cell["wall_ms"]
245
- print(f" p50={w['p50']}ms p95={w['p95']}ms -> {fname}")
252
+ gate = None
253
+ if args.compare_dir:
254
+ base_path = args.compare_dir / fname
255
+ if base_path.exists():
256
+ try:
257
+ baseline = json.loads(base_path.read_text(encoding="utf-8"))
258
+ gate = perf.regression_gate(
259
+ baseline,
260
+ cell,
261
+ max_regression=args.max_regression,
262
+ allow_incomparable=args.allow_incomparable,
263
+ )
264
+ (out_dir / f"{fname}.gate.json").write_text(
265
+ json.dumps(gate, indent=2) + "\n", encoding="utf-8"
266
+ )
267
+ if not gate.get("passed"):
268
+ failures.append((fname, gate))
269
+ except (OSError, ValueError) as exc:
270
+ failures.append((fname, {
271
+ "status": "incomparable",
272
+ "passed": False,
273
+ "reasons": [f"could not read baseline: {exc}"],
274
+ }))
275
+ _gate_text = f" gate={gate['status']}" if gate else ""
276
+ print(f" p50={w['p50']}ms p95={w['p95']}ms{_gate_text} -> {fname}")
246
277
 
247
278
  print(f"done. artifacts in {out_dir}")
279
+ if failures:
280
+ print("perf regression gate failed:", file=sys.stderr)
281
+ for fname, gate in failures:
282
+ detail = gate.get("failed_metrics") or gate.get("reasons")
283
+ print(f" {fname}: {gate.get('status')} {detail}", file=sys.stderr)
284
+ return 1
248
285
  return 0
249
286
 
250
287
 
@@ -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.6.0"
7
+ __version__ = "4.7.0"
@@ -354,7 +354,7 @@ of files) in minutes. Semantic analysis itself is sub-second; repo indexing domi
354
354
  ask --agent [dim]# full structured JSON for AI agents[/dim]
355
355
 
356
356
  [bold]Change and risk:[/bold]
357
- impact-chain <Class> . [dim]# blast radius w/ TX + security per hop[/dim]
357
+ impact-chain <Class> . [dim]# blast radius; TX/SEC findings opt-in[/dim]
358
358
  impact <Class> . [dim]# reverse deps → endpoints reached[/dim]
359
359
  pr-impact . --files - [dim]# same, scoped to a diff on stdin; gating codes[/dim]
360
360
  posture . --since main [dim]# did this branch open an endpoint? (exp.)[/dim]
@@ -8419,8 +8419,16 @@ def impact_chain_cmd(
8419
8419
  "surfaces). Totals stay exact in metadata/truncated_lists. "
8420
8420
  "0 disables the cap.",
8421
8421
  ),
8422
+ with_findings: bool = typer.Option(
8423
+ False, "--with-findings",
8424
+ help=(
8425
+ "Also run the repository-wide TX + security audit and filter findings "
8426
+ "to this chain. Off by default so query cost follows the queried "
8427
+ "subgraph rather than the whole repository."
8428
+ ),
8429
+ ),
8422
8430
  ) -> None:
8423
- """Spring impact-chain: systemic blast radius of a symbol with TX/SEC enrichment.
8431
+ """Spring impact-chain: systemic blast radius of a symbol; TX/SEC findings are opt-in.
8424
8432
 
8425
8433
  \b
8426
8434
  Given a symbol (class or method), returns:
@@ -8431,6 +8439,7 @@ def impact_chain_cmd(
8431
8439
  - security_surfaces — per-endpoint security verdict + confidence
8432
8440
  (security_posture authority) + declared policy + SEC findings
8433
8441
  - impact_findings — TX/SEC audit findings touching the call chain
8442
+ (only with --with-findings)
8434
8443
  - risk_level — critical | high | medium | low
8435
8444
 
8436
8445
  \b
@@ -8537,7 +8546,10 @@ def impact_chain_cmd(
8537
8546
  )
8538
8547
  return
8539
8548
 
8540
- result = run_impact_chain(cir, symbol, depth=depth, root=target, model=_model)
8549
+ result = run_impact_chain(
8550
+ cir, symbol, depth=depth, root=target, model=_model,
8551
+ include_findings=with_findings,
8552
+ )
8541
8553
 
8542
8554
  data = result.to_dict()
8543
8555
  # Same rule as `impact`: a file is a request for the whole answer (C2-2). An
@@ -457,6 +457,10 @@ class JavaDetector(AbstractDetector):
457
457
  or "resteasy" in text # RESTEasy — a JAX-RS implementation
458
458
  ):
459
459
  frameworks.append(FrameworkDetection(name="JAX-RS", source=source))
460
+ if "com.sun.jersey" in text or "org.glassfish.jersey" in text:
461
+ frameworks.append(FrameworkDetection(name="Jersey", source=source))
462
+ if "com.netflix.governator" in text:
463
+ frameworks.append(FrameworkDetection(name="Governator", source=source))
460
464
  if "mybatis" in text:
461
465
  frameworks.append(FrameworkDetection(name="MyBatis", source=source))
462
466
  if "thymeleaf" in text:
@@ -1324,13 +1324,14 @@ min_severity: "low" (default) | "medium" | "high" | "critical"
1324
1324
  """
1325
1325
 
1326
1326
  _IMPACT_CHAIN_DOC = """\
1327
- Spring impact-chain: blast radius of a symbol with TX/SEC semantic enrichment. JAVA/SPRING ONLY.
1327
+ Spring impact-chain: blast radius of a symbol. TX/SEC findings are opt-in. JAVA/SPRING ONLY.
1328
1328
 
1329
1329
  Do NOT call on non-Java repositories — returns resolution=not_found.
1330
1330
 
1331
1331
  Two query modes via query_type:
1332
1332
  "impact" (default) — BFS call graph: direct_callers, indirect_callers, endpoints_affected,
1333
- transaction_boundary, security_surfaces, impact_findings (TX/SEC patterns in call chain).
1333
+ transaction_boundary, security_surfaces. impact_findings are populated only when
1334
+ with_findings=true, because that runs a repository-wide TX + security audit.
1334
1335
  "events" — event topology: publishers, consumers, propagation graph for an event class
1335
1336
  or event publisher. Use when symbol is an event class (e.g. OrderPlacedEvent).
1336
1337
 
@@ -1344,6 +1345,7 @@ symbol: FQN, class name, or Class#method.
1344
1345
  repo_path: absolute path to the Java repository (default: current working directory).
1345
1346
  depth: BFS traversal depth 1–8 (default 4).
1346
1347
  query_type: "impact" (default) | "events"
1348
+ with_findings: false (default) | true to run the repository-wide TX + security audit
1347
1349
  """
1348
1350
 
1349
1351
  spring_audit = _alias_spec(
@@ -1374,7 +1376,7 @@ query_type: "impact" (default) | "events"
1374
1376
 
1375
1377
  impact_chain = _alias_spec(
1376
1378
  "impact_chain",
1377
- "Spring impact-chain: blast radius + TX/SEC enrichment. JAVA/SPRING ONLY.",
1379
+ "Spring impact-chain: blast radius; TX/SEC findings opt-in. JAVA/SPRING ONLY.",
1378
1380
  ("impact-chain",),
1379
1381
  (
1380
1382
  ToolParamSpec("symbol", "argument", str, required=True, default=None,
@@ -1386,6 +1388,9 @@ query_type: "impact" (default) | "events"
1386
1388
  ToolParamSpec("query_type", "option", str, required=False, default="impact",
1387
1389
  option_names=("--type",), choices=("impact", "events"),
1388
1390
  help="impact (default) = call-chain blast radius; events = event topology"),
1391
+ ToolParamSpec("with_findings", "option", bool, required=False, default=False,
1392
+ option_names=("--with-findings",), is_flag=True,
1393
+ help="Run TX + security audit and filter findings to this chain."),
1389
1394
  ),
1390
1395
  lambda inputs: [
1391
1396
  "impact-chain",
@@ -1393,7 +1398,7 @@ query_type: "impact" (default) | "events"
1393
1398
  str(inputs.get("repo_path", ".")),
1394
1399
  "--depth", str(inputs.get("depth", 4)),
1395
1400
  "--type", str(inputs.get("query_type", "impact")),
1396
- ],
1401
+ ] + (["--with-findings"] if inputs.get("with_findings") else []),
1397
1402
  supported_targets=("repo_path", "class_name"),
1398
1403
  unsupported_targets=("file_path",),
1399
1404
  validator=validate_repo_path,
@@ -883,24 +883,31 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
883
883
 
884
884
 
885
885
  @mcp.tool()
886
- def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) -> dict:
887
- """Spring impact-chain: systemic blast radius of a symbol with TX/SEC semantic enrichment. JAVA/SPRING ONLY.
886
+ def get_impact_chain(
887
+ repo_path: str = ".",
888
+ symbol: str = "",
889
+ depth: int = 4,
890
+ with_findings: bool = False,
891
+ ) -> dict:
892
+ """Spring impact-chain: systemic blast radius of a symbol. TX/SEC findings are opt-in. JAVA/SPRING ONLY.
888
893
 
889
894
  Do NOT call this on non-Java repositories — it will return resolution=not_found.
890
895
 
891
- Maps to: ask impact-chain <symbol> <repo_path> [--depth <depth>]
896
+ Maps to: ask impact-chain <symbol> <repo_path> [--depth <depth>] [--with-findings]
892
897
  Returns: ImpactChainResult with schema_version, symbol, resolution,
893
898
  direct_callers, indirect_callers, endpoints_affected,
894
899
  transaction_boundary (propagation/isolation/read_only),
895
900
  security_surfaces (per-endpoint verdict/confidence from the
896
901
  security_posture authority + declared policy + finding IDs),
897
- impact_findings (TX-001..005 + SEC-001..003 findings in call chain),
902
+ impact_findings (TX/SEC/DEAD findings in call chain, only when
903
+ with_findings=true),
898
904
  analysis_warnings, risk_level, confidence, metadata.
899
905
 
900
906
  symbol: FQN, class name, or Class#method. Examples:
901
907
  "OrderService", "com.example.OrderService#placeOrder"
902
908
  repo_path: absolute path to the Java repository (default: current working directory).
903
909
  depth: BFS depth for indirect caller traversal (1–8, default: 4).
910
+ with_findings: run repository-wide TX + security audit and filter findings to this chain.
904
911
  """
905
912
  _raw = repo_path
906
913
  try:
@@ -915,6 +922,8 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
915
922
  if _path_err is not None:
916
923
  return _path_err
917
924
  args = ["impact-chain", symbol.strip(), repo_path, "--depth", str(depth)]
925
+ if with_findings:
926
+ args.append("--with-findings")
918
927
  timeout_ms = int(os.environ.get("SOURCECODE_IMPACT_TIMEOUT_MS", str(_DEFAULT_IMPACT_TIMEOUT_MS)))
919
928
  timeout_s = timeout_ms / 1000.0
920
929
  _exec = concurrent.futures.ThreadPoolExecutor(max_workers=1)
@@ -1472,6 +1472,11 @@ class MigrationReport:
1472
1472
  # dimension scores it aggregates) so the headline number is fully traceable.
1473
1473
  readiness_aggregate: dict = field(default_factory=dict)
1474
1474
  blocking_count: int = 0
1475
+ # Critical/high product findings on dimensions that participate in the
1476
+ # migration-readiness headline. `blocking_count` remains the broad product
1477
+ # blocker count for backward compatibility; this one excludes orthogonal JDK
1478
+ # upkeep that never feeds readiness_score.
1479
+ migration_blocking_count: int = 0
1475
1480
  # C1-12: null when the total is not a point (a range slice contributes to it).
1476
1481
  # The span always exists — read estimated_effort_range_days for the number.
1477
1482
  estimated_effort_days: Optional[float] = 0.0
@@ -1567,6 +1572,11 @@ class MigrationReport:
1567
1572
  self.blocking_count = sum(
1568
1573
  1 for f in main_findings if f.severity in ("critical", "high")
1569
1574
  )
1575
+ self.migration_blocking_count = sum(
1576
+ 1 for f in main_findings
1577
+ if f.severity in ("critical", "high")
1578
+ and f.migration_target in _BOOT3_MIGRATION_TARGETS
1579
+ )
1570
1580
 
1571
1581
  # Per-dimension readiness — independent severity-weighted scores (MAIN only).
1572
1582
  self.jakarta_readiness = _dimension_score(main_findings, _JAKARTA_TARGETS)
@@ -1941,6 +1951,7 @@ class MigrationReport:
1941
1951
  "by_rule": by_rule,
1942
1952
  "by_migration_target": by_target,
1943
1953
  "main_findings": len(main_findings),
1954
+ "migration_blocking_findings": self.migration_blocking_count,
1944
1955
  "non_blocking_findings": self.non_blocking["count"],
1945
1956
  "hygiene_findings": self.hygiene_findings,
1946
1957
  }
@@ -1993,10 +2004,21 @@ class MigrationReport:
1993
2004
  "(jakarta / boot3 / hibernate); see readiness_aggregate for the exact "
1994
2005
  "inputs. N/A dimensions are excluded (never counted as 0); "
1995
2006
  "jdk_modernization is orthogonal upkeep and is NOT in the aggregate. "
1996
- "For decisions read the per-dimension breakdown + blocking_count."
2007
+ "For decisions read the per-dimension breakdown + "
2008
+ "migration_blocking_count."
1997
2009
  ),
1998
2010
  "headline_blocker": self.headline_blocker,
1999
2011
  "blocking_count": self.blocking_count,
2012
+ "blocking_count_basis": (
2013
+ "critical/high findings in product code across every target, including "
2014
+ "orthogonal JDK modernization"
2015
+ ),
2016
+ "migration_blocking_count": self.migration_blocking_count,
2017
+ "migration_blocking_count_basis": (
2018
+ "critical/high product-code findings whose targets feed the migration "
2019
+ "readiness aggregate (jakarta / boot3 / spring_security_6). Excludes "
2020
+ "orthogonal JDK modernization."
2021
+ ),
2000
2022
  "estimated_effort_days": self.estimated_effort_days,
2001
2023
  "estimated_effort_range_days": self.estimated_effort_range_days,
2002
2024
  "estimated_effort_note": (
@@ -2116,6 +2138,7 @@ class MigrationReport:
2116
2138
  f"(readiness_score reflects jakarta/Boot3 only — Hibernate is a separate rewrite axis)"]
2117
2139
  if self.headline_blocker else []),
2118
2140
  f"Spring present: {self.spring_present} Spring Boot 2 detected: {_boot}",
2141
+ f"Migration blockers (readiness dimensions): {self.migration_blocking_count}",
2119
2142
  f"Blocking issues (product code): {self.blocking_count} "
2120
2143
  f"(critical: {main_crit}, high: {main_high})"
2121
2144
  + (f" [+{nb} in test/generated, non-blocking]" if nb else ""),
@@ -37,6 +37,9 @@ SCHEMA_VERSION = "perf-baseline-v1"
37
37
  #: guesses: repo discovery, what-is-this-repo detection+enrichment, model
38
38
  #: assembly + semantic/contract extraction, and output rendering.
39
39
  PHASES: tuple[str, ...] = ("discovery", "detection", "analysis", "serialize")
40
+ SCALE_DIMENSIONS: tuple[str, ...] = (
41
+ "files", "symbols", "classes", "methods", "endpoints", "findings",
42
+ )
40
43
 
41
44
  #: Env var that arms the in-process recorder.
42
45
  ENV_ENABLE = "SOURCECODE_PERF"
@@ -248,12 +251,24 @@ def build_baseline_cell(
248
251
  phase_ms[ph] = None
249
252
  phase_ms["instrumentation"] = "absent"
250
253
 
254
+ repo_dict = dict(repo)
255
+ scale_dimensions = {
256
+ key: repo_dict[key]
257
+ for key in SCALE_DIMENSIONS
258
+ if key in repo_dict
259
+ }
260
+
251
261
  return {
252
262
  "schema_version": SCHEMA_VERSION,
253
263
  "tool_version": tool_version,
254
264
  "captured_at": captured_at or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
255
265
  "command": command,
256
- "repo": dict(repo),
266
+ "repo": repo_dict,
267
+ "scale_dimensions": scale_dimensions,
268
+ "scale_dimensions_basis": (
269
+ "The repository axes this timing is expected to scale over. A performance "
270
+ "claim without these dimensions is not comparable across repositories."
271
+ ),
257
272
  "mode": mode,
258
273
  "runs": len(wall_ms_runs),
259
274
  "wall_ms": {
@@ -266,3 +281,99 @@ def build_baseline_cell(
266
281
  "rss_peak_bytes": _stat_block(rss_bytes_runs) if rss_bytes_runs else None,
267
282
  "env": dict(env) if env is not None else collect_env(),
268
283
  }
284
+
285
+
286
+ def regression_gate(
287
+ baseline: Mapping[str, object],
288
+ current: Mapping[str, object],
289
+ *,
290
+ max_regression: float = 0.25,
291
+ allow_incomparable: bool = False,
292
+ ) -> dict[str, object]:
293
+ """Compare two perf cells and decide whether the current run regressed.
294
+
295
+ Same-host comparability is mandatory by default. Timing deltas compare p50
296
+ and p95 wall time; either one breaching the threshold fails the gate.
297
+ """
298
+ comparable, reasons = _perf_comparability(baseline, current)
299
+ if not comparable and not allow_incomparable:
300
+ return {
301
+ "status": "incomparable",
302
+ "passed": False,
303
+ "reasons": reasons,
304
+ "threshold": max_regression,
305
+ "basis": (
306
+ "Perf regressions are gated only between comparable cells: same "
307
+ "schema, command, repo, mode and hostname_hash."
308
+ ),
309
+ }
310
+
311
+ def _wall(cell: Mapping[str, object], key: str) -> float | None:
312
+ wall = cell.get("wall_ms")
313
+ if not isinstance(wall, Mapping):
314
+ return None
315
+ val = wall.get(key)
316
+ return float(val) if isinstance(val, (int, float)) else None
317
+
318
+ deltas: dict[str, object] = {}
319
+ failures: list[str] = []
320
+ for key in ("p50", "p95"):
321
+ base = _wall(baseline, key)
322
+ head = _wall(current, key)
323
+ if base is None or head is None or base <= 0:
324
+ deltas[key] = {"status": "unmeasured", "baseline": base, "current": head}
325
+ continue
326
+ ratio = (head - base) / base
327
+ deltas[key] = {
328
+ "baseline": base,
329
+ "current": head,
330
+ "delta": round(head - base, 3),
331
+ "regression_ratio": round(ratio, 4),
332
+ "regression_percent": round(ratio * 100.0, 2),
333
+ }
334
+ if ratio > max_regression:
335
+ failures.append(key)
336
+
337
+ return {
338
+ "status": "fail" if failures else "pass",
339
+ "passed": not failures,
340
+ "threshold": max_regression,
341
+ "failed_metrics": failures,
342
+ "deltas": deltas,
343
+ "scale_dimensions": {
344
+ "baseline": baseline.get("scale_dimensions")
345
+ or (
346
+ baseline.get("repo")
347
+ if isinstance(baseline.get("repo"), Mapping) else {}
348
+ ),
349
+ "current": current.get("scale_dimensions")
350
+ or (
351
+ current.get("repo")
352
+ if isinstance(current.get("repo"), Mapping) else {}
353
+ ),
354
+ },
355
+ "basis": (
356
+ "Fails when current wall_ms.p50 or wall_ms.p95 exceeds the comparable "
357
+ f"baseline by more than {max_regression:.0%}. Unexplained regressions "
358
+ "must be attributed or accepted by updating the baseline deliberately."
359
+ ),
360
+ }
361
+
362
+
363
+ def _perf_comparability(
364
+ baseline: Mapping[str, object],
365
+ current: Mapping[str, object],
366
+ ) -> tuple[bool, list[str]]:
367
+ reasons: list[str] = []
368
+ for key in ("schema_version", "command", "mode"):
369
+ if baseline.get(key) != current.get(key):
370
+ reasons.append(f"{key} differs")
371
+ base_repo = baseline.get("repo") if isinstance(baseline.get("repo"), Mapping) else {}
372
+ cur_repo = current.get("repo") if isinstance(current.get("repo"), Mapping) else {}
373
+ if base_repo.get("name") != cur_repo.get("name"):
374
+ reasons.append("repo.name differs")
375
+ base_env = baseline.get("env") if isinstance(baseline.get("env"), Mapping) else {}
376
+ cur_env = current.get("env") if isinstance(current.get("env"), Mapping) else {}
377
+ if base_env.get("hostname_hash") != cur_env.get("hostname_hash"):
378
+ reasons.append("env.hostname_hash differs")
379
+ return not reasons, reasons
@@ -34,7 +34,7 @@ class Rule:
34
34
  """One audit rule, as a reader meets it."""
35
35
 
36
36
  id: str
37
- family: str # "TX" or "SEC" — the scope flag that selects it
37
+ family: str # "TX", "SEC", "DEAD" — the help grouping
38
38
  summary: str # one line, no wrapping games
39
39
 
40
40
  def to_dict(self) -> dict:
@@ -55,9 +55,16 @@ RULES: tuple[Rule, ...] = (
55
55
  Rule("SEC-005", "SEC", "CSRF disabled under session-bearing authentication"),
56
56
  Rule("SEC-006", "SEC", "Cookie created without the Secure attribute"),
57
57
  Rule("SEC-007", "SEC", "Credential stored in a deployment descriptor"),
58
+ Rule("SEC-008", "SEC", "SQL built by MyBatis string interpolation"),
59
+ Rule("DEAD-001", "DEAD", "Security control present but disabled"),
58
60
  )
59
61
 
60
- FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC")
62
+ FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC", "DEAD")
63
+ _FAMILY_LABELS = {
64
+ "TX": "TX anomalies",
65
+ "SEC": "security surface",
66
+ "DEAD": "disabled controls",
67
+ }
61
68
 
62
69
 
63
70
  def ids() -> tuple[str, ...]:
@@ -84,7 +91,7 @@ def range_label(family: str) -> str:
84
91
  def summary_line() -> str:
85
92
  """The command's one-liner: the families and how far each one reaches."""
86
93
  return " + ".join(
87
- f"{'TX anomalies' if fam == 'TX' else 'security surface'} "
94
+ f"{_FAMILY_LABELS.get(fam, fam)} "
88
95
  f"({range_label(fam)})"
89
96
  for fam in FAMILY_ORDER
90
97
  if for_family(fam)