sourcecode 4.11.0__tar.gz → 4.14.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 (232) hide show
  1. sourcecode-4.14.0/.github/workflows/perf-gate.yml +109 -0
  2. {sourcecode-4.11.0 → sourcecode-4.14.0}/CHANGELOG.md +189 -0
  3. {sourcecode-4.11.0 → sourcecode-4.14.0}/PKG-INFO +3 -3
  4. {sourcecode-4.11.0 → sourcecode-4.14.0}/README.md +2 -2
  5. {sourcecode-4.11.0 → sourcecode-4.14.0}/pyproject.toml +1 -1
  6. sourcecode-4.14.0/scripts/perf_gate.py +176 -0
  7. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/__init__.py +1 -1
  8. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/architectural_baseline.py +50 -2
  9. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/cache_model.py +83 -16
  10. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/cli.py +425 -28
  11. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/container_wiring.py +21 -0
  12. sourcecode-4.14.0/src/sourcecode/mcp/dependency_import.py +87 -0
  13. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/registry.py +6 -1
  14. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/server.py +20 -2
  15. sourcecode-4.14.0/src/sourcecode/parallel.py +241 -0
  16. sourcecode-4.14.0/src/sourcecode/parse_cache.py +521 -0
  17. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/perf.py +268 -4
  18. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/phased_run.py +61 -10
  19. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/progress.py +116 -46
  20. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/readiness_timeline.py +12 -1
  21. sourcecode-4.14.0/src/sourcecode/readonly.py +171 -0
  22. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/release_info.py +1 -1
  23. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/repository_ir.py +44 -3
  24. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_impact.py +5 -2
  25. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/risk.py +120 -38
  26. sourcecode-4.14.0/src/sourcecode/rule_pass.py +236 -0
  27. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/runs.py +3 -0
  28. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/security_config_scan.py +36 -5
  29. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_findings.py +82 -0
  30. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_impact.py +29 -3
  31. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_security_audit.py +146 -9
  32. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_tx_analyzer.py +110 -10
  33. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/verify_edit.py +3 -0
  34. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/verify_repo.py +62 -9
  35. sourcecode-4.11.0/src/sourcecode/cache.tmp_new +0 -772
  36. sourcecode-4.11.0/src/sourcecode/parse_cache.py +0 -180
  37. {sourcecode-4.11.0 → sourcecode-4.14.0}/.github/workflows/build-windows.yml +0 -0
  38. {sourcecode-4.11.0 → sourcecode-4.14.0}/.gitignore +0 -0
  39. {sourcecode-4.11.0 → sourcecode-4.14.0}/.ruff.toml +0 -0
  40. {sourcecode-4.11.0 → sourcecode-4.14.0}/CLAUDE.md +0 -0
  41. {sourcecode-4.11.0 → sourcecode-4.14.0}/CONTRIBUTING.md +0 -0
  42. {sourcecode-4.11.0 → sourcecode-4.14.0}/LICENSE +0 -0
  43. {sourcecode-4.11.0 → sourcecode-4.14.0}/SECURITY.md +0 -0
  44. {sourcecode-4.11.0 → sourcecode-4.14.0}/raw +0 -0
  45. {sourcecode-4.11.0 → sourcecode-4.14.0}/scripts/compare_integration_engines.py +0 -0
  46. {sourcecode-4.11.0 → sourcecode-4.14.0}/scripts/customer_smoke_test.sh +0 -0
  47. {sourcecode-4.11.0 → sourcecode-4.14.0}/scripts/generate_jdk_exports.py +0 -0
  48. {sourcecode-4.11.0 → sourcecode-4.14.0}/scripts/perf_harness.py +0 -0
  49. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/adaptive_scanner.py +0 -0
  50. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/archetype.py +0 -0
  51. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/architectural_delta.py +0 -0
  52. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/architecture_analyzer.py +0 -0
  53. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/architecture_summary.py +0 -0
  54. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/ast_extractor.py +0 -0
  55. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/audit_report.py +0 -0
  56. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/baseline_autocapture.py +0 -0
  57. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/cache.py +0 -0
  58. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/call_surface.py +0 -0
  59. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/caller_metrics.py +0 -0
  60. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/caller_reach.py +0 -0
  61. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/canonical_ir.py +0 -0
  62. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/chain_rules.py +0 -0
  63. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/change_plan.py +0 -0
  64. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/cir_graphs.py +0 -0
  65. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/classifier.py +0 -0
  66. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/client_calls.py +0 -0
  67. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  68. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/compare.py +0 -0
  69. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/confidence_analyzer.py +0 -0
  70. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/constraint_diff.py +0 -0
  71. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/context_cache.py +0 -0
  72. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/context_graph.py +0 -0
  73. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/context_scorer.py +0 -0
  74. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/context_summarizer.py +0 -0
  75. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/contract_diff.py +0 -0
  76. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/contract_init.py +0 -0
  77. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/contract_model.py +0 -0
  78. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/contract_pipeline.py +0 -0
  79. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/coverage_parser.py +0 -0
  80. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/data_exposure.py +0 -0
  81. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/data_labels.py +0 -0
  82. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/defect_identity.py +0 -0
  83. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/degradation.py +0 -0
  84. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/dependency_analyzer.py +0 -0
  85. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/deployment_prefix.py +0 -0
  86. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detach.py +0 -0
  87. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/__init__.py +0 -0
  88. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/base.py +0 -0
  89. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  90. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/dart.py +0 -0
  91. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/dotnet.py +0 -0
  92. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/elixir.py +0 -0
  93. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/go.py +0 -0
  94. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/heuristic.py +0 -0
  95. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/hybrid.py +0 -0
  96. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/java.py +0 -0
  97. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  98. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/nodejs.py +0 -0
  99. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/parsers.py +0 -0
  100. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/php.py +0 -0
  101. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/project.py +0 -0
  102. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/python.py +0 -0
  103. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/ruby.py +0 -0
  104. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/rust.py +0 -0
  105. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/systems.py +0 -0
  106. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/terraform.py +0 -0
  107. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/detectors/tooling.py +0 -0
  108. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/doc_analyzer.py +0 -0
  109. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  110. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/endpoint_literals.py +0 -0
  111. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/endpoint_metrics.py +0 -0
  112. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  113. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/env_analyzer.py +0 -0
  114. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/envelope.py +0 -0
  115. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/environment_resolution.py +0 -0
  116. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/error_schema.py +0 -0
  117. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/evidence_provider.py +0 -0
  118. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/execution_plan.py +0 -0
  119. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/explain.py +0 -0
  120. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/facts/__init__.py +0 -0
  121. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/facts/registry.json +0 -0
  122. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/file_chunker.py +0 -0
  123. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/file_classifier.py +0 -0
  124. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/filter_surface.py +0 -0
  125. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/format_contract.py +0 -0
  126. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/fqn_utils.py +0 -0
  127. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/git_analyzer.py +0 -0
  128. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/git_checkout.py +0 -0
  129. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/graph_analyzer.py +0 -0
  130. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/graph_evidence.py +0 -0
  131. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/hibernate_strat.py +0 -0
  132. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/identity_fallback.py +0 -0
  133. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/integration_coordinates.py +0 -0
  134. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/jdk_exports.py +0 -0
  135. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/license.py +0 -0
  136. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/__init__.py +0 -0
  137. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  138. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  139. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  140. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  141. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  142. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/orchestrator.py +0 -0
  143. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp/runner.py +0 -0
  144. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/mcp_nudge.py +0 -0
  145. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/metrics_analyzer.py +0 -0
  146. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/migrate_check.py +0 -0
  147. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/migration_blast.py +0 -0
  148. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/non_coverage.py +0 -0
  149. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/openapi_surface.py +0 -0
  150. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/openrewrite_recipe.py +0 -0
  151. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/output_budget.py +0 -0
  152. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/output_encoding.py +0 -0
  153. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/path_admission.py +0 -0
  154. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/path_filters.py +0 -0
  155. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/pipe_contract.py +0 -0
  156. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/posture.py +0 -0
  157. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/pr_comment_renderer.py +0 -0
  158. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/pr_impact.py +0 -0
  159. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/prepare_context.py +0 -0
  160. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/provenance.py +0 -0
  161. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/ranking_engine.py +0 -0
  162. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/reconciliation.py +0 -0
  163. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/redactor.py +0 -0
  164. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/reference_facts.py +0 -0
  165. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/relevance_scorer.py +0 -0
  166. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/remedies.py +0 -0
  167. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/rename_refactor.py +0 -0
  168. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/repo_classifier.py +0 -0
  169. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/__init__.py +0 -0
  170. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/context.py +0 -0
  171. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/errors.py +0 -0
  172. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/executor.py +0 -0
  173. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/planner.py +0 -0
  174. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/query.py +0 -0
  175. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/request.py +0 -0
  176. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/resolution.py +0 -0
  177. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/result.py +0 -0
  178. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/retriever.py +0 -0
  179. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/runtime.py +0 -0
  180. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps.py +0 -0
  181. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  182. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  183. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  184. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  185. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  186. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/ris.py +0 -0
  187. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/rule_catalog.py +0 -0
  188. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/runtime_classifier.py +0 -0
  189. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/sarif.py +0 -0
  190. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/scanner.py +0 -0
  191. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/schema.py +0 -0
  192. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  193. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/security_config.py +0 -0
  194. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/security_posture.py +0 -0
  195. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/semantic_analyzer.py +0 -0
  196. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  197. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  198. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/semantic_services.py +0 -0
  199. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/serializer.py +0 -0
  200. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/servlet_surface.py +0 -0
  201. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/source_text.py +0 -0
  202. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_event_topology.py +0 -0
  203. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_model.py +0 -0
  204. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_profiles.py +0 -0
  205. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_properties.py +0 -0
  206. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/spring_semantic.py +0 -0
  207. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/summarizer.py +0 -0
  208. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/target_admission.py +0 -0
  209. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/__init__.py +0 -0
  210. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/config.py +0 -0
  211. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/consent.py +0 -0
  212. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/events.py +0 -0
  213. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/filters.py +0 -0
  214. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/telemetry/transport.py +0 -0
  215. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/test_gap_ranking.py +0 -0
  216. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/test_sources.py +0 -0
  217. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/text_input.py +0 -0
  218. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/token_estimate.py +0 -0
  219. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/tree_utils.py +0 -0
  220. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/type_usage_surface.py +0 -0
  221. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/validation_inference.py +0 -0
  222. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/validation_surface.py +0 -0
  223. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/verify_rules.py +0 -0
  224. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/version_check.py +0 -0
  225. {sourcecode-4.11.0 → sourcecode-4.14.0}/src/sourcecode/workspace.py +0 -0
  226. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/functions/README.md +0 -0
  227. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/functions/get-license/index.ts +0 -0
  228. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  229. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/functions/telemetry/index.ts +0 -0
  230. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/sql/license_event_ordering.sql +0 -0
  231. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/sql/licensing_schema.sql +0 -0
  232. {sourcecode-4.11.0 → sourcecode-4.14.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -0,0 +1,109 @@
1
+ name: Performance regression gate
2
+
3
+ # F-AP. Field evaluation #16 measured seven commands 2-3.4x slower on a minor
4
+ # release while every payload stayed byte-identical, and named the cause:
5
+ # "no es que haya regresiones - es que no hay nada que las detecte".
6
+ # This is the detector. The threshold, the repository and the gated command
7
+ # population are declared in `sourcecode.perf` and documented in
8
+ # `docs/perf/REGRESSION-GATE.md`; nothing here carries a number of its own.
9
+
10
+ on:
11
+ schedule:
12
+ # Nightly, after the day's merges have settled.
13
+ - cron: "0 3 * * *"
14
+ workflow_dispatch:
15
+ inputs:
16
+ baseline_ref:
17
+ description: "Baseline directory under docs/perf/baselines (default: gate-latest)"
18
+ required: false
19
+ default: "gate-latest"
20
+
21
+ concurrency:
22
+ group: perf-gate
23
+ cancel-in-progress: false
24
+
25
+ jobs:
26
+ gate:
27
+ runs-on: ubuntu-latest
28
+ timeout-minutes: 120
29
+ env:
30
+ # Declares the host class so nightly runs on ephemeral runners are
31
+ # comparable at all. Recorded in every artifact as `env.host_id_declared`,
32
+ # so the declaration is visible rather than silent.
33
+ SOURCECODE_PERF_HOST_ID: github-ubuntu-latest
34
+ BASELINE_DIR: docs/perf/baselines/${{ github.event.inputs.baseline_ref || 'gate-latest' }}
35
+
36
+ steps:
37
+ - name: Checkout
38
+ uses: actions/checkout@v4
39
+
40
+ - name: Setup Python
41
+ uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.11"
44
+
45
+ - name: Install
46
+ run: |
47
+ python -m pip install --upgrade pip
48
+ pip install .
49
+
50
+ - name: Prove the gate is armed
51
+ # A green gate is evidence only when a red one is reachable in the same
52
+ # job with the same constants. This step fails if a synthetic 3x-slower
53
+ # cell would have passed.
54
+ run: python3 scripts/perf_gate.py --self-test
55
+
56
+ - name: Read the declared gate configuration
57
+ id: cfg
58
+ run: |
59
+ python3 scripts/perf_gate.py --print-config | tee gate-config.json
60
+ echo "repo_url=$(python3 -c "import json;print(json.load(open('gate-config.json'))['repository_url'])")" >> "$GITHUB_OUTPUT"
61
+ echo "repo=$(python3 -c "import json;print(json.load(open('gate-config.json'))['repository'])")" >> "$GITHUB_OUTPUT"
62
+ echo "mode=$(python3 -c "import json;print(json.load(open('gate-config.json'))['mode'])")" >> "$GITHUB_OUTPUT"
63
+ echo "runs=$(python3 -c "import json;print(json.load(open('gate-config.json'))['min_runs'])")" >> "$GITHUB_OUTPUT"
64
+ echo "commands=$(python3 -c "import json;print(' '.join(repr(c) for c in json.load(open('gate-config.json'))['commands']))")" >> "$GITHUB_OUTPUT"
65
+
66
+ - name: Clone the gate repository
67
+ run: |
68
+ mkdir -p fleet
69
+ git clone --depth 1 "${{ steps.cfg.outputs.repo_url }}" "fleet/${{ steps.cfg.outputs.repo }}"
70
+
71
+ - name: Measure
72
+ env:
73
+ PERF_FLEET_ROOT: fleet
74
+ run: |
75
+ python3 scripts/perf_harness.py \
76
+ --repos "${{ steps.cfg.outputs.repo }}" \
77
+ --commands ${{ steps.cfg.outputs.commands }} \
78
+ --modes "${{ steps.cfg.outputs.mode }}" \
79
+ --runs "${{ steps.cfg.outputs.runs }}" \
80
+ --fleet-root fleet \
81
+ --out perf-run
82
+
83
+ - name: Gate
84
+ run: |
85
+ python3 scripts/perf_gate.py \
86
+ --baseline "$BASELINE_DIR" \
87
+ --current perf-run \
88
+ --report perf-run/gate.json \
89
+ --table perf-run/RELEASE-TABLE.md
90
+
91
+ - name: Publish the measured table
92
+ if: always()
93
+ run: |
94
+ if [ -f perf-run/RELEASE-TABLE.md ]; then
95
+ {
96
+ echo "## Measured wall time per command"
97
+ echo
98
+ cat perf-run/RELEASE-TABLE.md
99
+ } >> "$GITHUB_STEP_SUMMARY"
100
+ fi
101
+
102
+ - name: Upload artifacts
103
+ if: always()
104
+ uses: actions/upload-artifact@v4
105
+ with:
106
+ name: perf-gate-${{ github.run_number }}
107
+ path: |
108
+ perf-run/**
109
+ gate-config.json
@@ -2,6 +2,195 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.14.0] — 2026-08-09
6
+
7
+ **The Part 1 queue of Amendment N — the four rows field evaluation #17 opened,
8
+ one commit each.** The fourth measurement of the same repository at the same
9
+ commit scored **7,5/10**, the highest of the series, and verified six of the
10
+ previous release's fixes from the outside. Both rows it opened are in the fields
11
+ that describe ASK's *own* reliability, which is where this product can least
12
+ afford to be wrong.
13
+
14
+ **Fixed in this release:**
15
+
16
+ - **A truncated answer no longer raises its own confidence (C2-31).** Three
17
+ complete runs published 395 findings / 98 defects at `confidence_level:
18
+ medium`; the run the budget cut published 289 / 15 at **`high`**, because the
19
+ level is derived from the confidence of the high/critical findings *that
20
+ exist* and the family contributing the medium-confidence ones is the family
21
+ that did not run. `SpringAuditResult.partial` is now one flag — set by the
22
+ scopes, by the CLI for a phase that never started, and carried through
23
+ `merge` — and a partial answer is capped at `low` with `confidence_basis`
24
+ saying the cut is the reason. `summary` gains `partial`, `counts_are_floor`
25
+ and `counts_basis`, so the mark lives where the numbers a consumer reads are,
26
+ not only in `_partial` at the root. The cap is a ceiling and never a floor: a
27
+ complete run keeps its level and gains no fields.
28
+ - **The rule inventory names rules, and both scopes' (C1-42).** `rules_run`
29
+ listed four SEC ids in a payload whose findings carried DEAD-001, SEC-008,
30
+ TX-006, TX-001, SEC-005 and SEC-006, and `rules_not_run` said `[GATE-001]`
31
+ where the arithmetic proves GATE-004 did not run either. The lists were keyed
32
+ on the pattern *object's* identity, so a family emitting six rules stood for
33
+ itself, and `merge` overwrote the TX scope's inventory with the security
34
+ scope's. `rule_pass.rule_ids_of()` is the one authority, the two lists are
35
+ unioned across scopes, and `rules_run_count` / `rules_total` sit beside them.
36
+ Three invariants are asserted over a real audit: every rule that fired is in
37
+ `rules_run`, the lists never overlap, and a complete `--scope all` run
38
+ inventories exactly `rule_catalog.ids()`.
39
+ - **The heartbeat is emitted by the work, not only by a thread (C3-80).** Four
40
+ versions with the same shape: one line at `elapsed=20.1s`, 39 minutes of
41
+ nothing, then the same stage text seconds before the end. A line-mode
42
+ heartbeat printed only by a daemon thread depends on that thread being
43
+ scheduled during exactly the CPU-bound stretch the operator is asking about.
44
+ `Progress._emit()` is now one seam with one throttle and two callers — the
45
+ loop on its timer and the analysis in-band — sharing one `_last_emit` stamp,
46
+ so the line rate is unchanged and a running analysis is always heard from.
47
+ - **The budget cuts inside a rule family (C3-79).** Budget 420 s, elapsed
48
+ 2 376 s: `exhausted()` was asked on a whole-family boundary and one family
49
+ lasts 39 minutes. `RulePassProgress.tick()` reports counted work and answers
50
+ *"is the time spent"* in one call, sampling the clock once per 64 units; five
51
+ families that walk a population take a `FamilyBudget` and stop on a batch
52
+ boundary. A family cut in flight is in neither list — it is
53
+ `rules_partially_run` with the count it reached, and the findings it produced
54
+ are kept, because they were measured. Measured on BroadleafCommerce: a 4,5 s
55
+ budget stops at 1 408 of 2 766 Java files and ends at 5,1 s.
56
+
57
+ **Verified in the field, not changed here:** `verify` exiting 2 without contracts
58
+ is C3-75's deliberate half (the waiver `--allow-unverified` and the named
59
+ `unverified_reason` are the fix; a distinct code is a contract change, F-AV), and
60
+ the 4.10.7 performance that never came back is C3-76, which does not reproduce on
61
+ this platform and waits on the Windows runner (F-AW).
62
+
63
+ Suite 7 787.
64
+
65
+ ## [4.13.0] — 2026-08-08
66
+
67
+ **The capability queue of Amendment M — the four features field evaluation #16
68
+ asked for, one commit each, after Part 1 closed in 4.12.0.** The evaluation's
69
+ verdict on engineering was *"no es que haya regresiones — es que no hay nada que
70
+ las detecte"*, and its "if you could only do two things" was F-AP and F-AQ, in
71
+ that order. Both ship here, with F-AR and F-AS beside them.
72
+
73
+ **New in this release:**
74
+
75
+ - **A release performance gate (F-AP).** `perf.gate_report()` turns a whole run
76
+ into one verdict against a whole baseline, and three of its four outcomes are
77
+ not a pass: a cell over the threshold **fails**, a baselined cell this run did
78
+ not measure **fails** (silence is how "nothing detects a regression" happens),
79
+ and a cell under the ADR-0006 sample floor **fails**. `scripts/perf_gate.py
80
+ --self-test` proves the gate is armed by failing a synthetic 3×-slower cell —
81
+ CI runs it *before* it runs the measurement, because a green gate is evidence
82
+ only when a red one is reachable on the same constants. Threshold **20 %**,
83
+ warm mode, one ~3 000-file repository, six repository-wide commands, and the
84
+ three exclusions published with every verdict. All of it lives in
85
+ `sourcecode.perf`; `docs/perf/REGRESSION-GATE.md` embeds `--print-config`'s
86
+ output verbatim and a test asserts they are the same bytes.
87
+ - **The measured times are published, generated (F-AP, second rider).**
88
+ `perf.release_table()` builds the per-command / per-repository-scale table a
89
+ release note carries, from the run's own artifacts. An axis that was not
90
+ measured prints `unknown`, never `0`. And `cache model`'s figures now name the
91
+ build they were measured on (3.2.2) beside the build you are running, so a
92
+ table nine releases old cannot read as this build's measurement.
93
+ - **`--jobs N` / `-j` / `ASK_JOBS` (F-AQ)**, default `cpu_count() - 1`. The
94
+ fourth consecutive evaluation to ask for parallelism was the first to sample
95
+ it: `threads=1`, `CPU/wall = 0,92…0,95`, 54 minutes, 20 cores. Worker
96
+ processes now parse files into the *same* content-addressed cache the IR loop
97
+ is about to read, so the loop is untouched and **byte-identity is structural,
98
+ not inspected** — no worker output reaches the payload by any route. Measured
99
+ on 2 766 Java files, both sides parse-cold with their own cache root: 5,36 s →
100
+ 3,46 s (**1,55×**), CPU/wall 2,07, IR hash identical. Every failure mode — no
101
+ pool, a faulting worker, a frozen build, a disabled cache, a repository under
102
+ the 200-file floor — degrades to today's sequential parse and returns a receipt
103
+ naming the reason.
104
+ - **`ASK_PARSE_CACHE_MAX_MB` (F-AR)**, default **512 MB**, least-recently-used,
105
+ enforced on write and published in `cache status` beside the size it bounds.
106
+ C3-70 retires the generation that can never be read again — an exact rule, and
107
+ exact rules only cover what they can foresee. This bounds what they cannot:
108
+ many repositories, many branches, one machine, and an owner who never runs
109
+ `ask cache clear`. Reads refresh recency (hourly granularity, so a hit is not a
110
+ disk write), an entry and its body-fact blob are evicted together, and
111
+ `ASK_PARSE_CACHE_MAX_MB=0` turns the guard off deliberately and says so.
112
+ - **`--no-write` / `ASK_READONLY=1` (F-AS)** — nothing is created inside the
113
+ analysed repository, and a command that cannot answer without writing says so
114
+ instead of writing, in the same error envelope every other refusal uses.
115
+ Accepted by **every** command (consumed before the parser, the `--detach`
116
+ precedent), because a mode five commands accept and seventy-nine reject is a
117
+ mode whose one refusing run is the run that writes. Each refusal is announced
118
+ once on stderr with its path. Shared caches outside the repository are
119
+ untouched — an auditor is protecting a checkout, not a home directory.
120
+ - **`ask --help` names every command that writes into the repository**, with the
121
+ condition beside it ("only with `--snapshot`"), generated from one authority.
122
+
123
+ **Measured, not fixed, and said so:** the second half of F-AQ — *"one audit per
124
+ run, shared by the compositions"* — was measured before being touched.
125
+ Instrumenting `build_risk` on a real repository counts `run_tx_audit` **1×** and
126
+ `run_security_audit` **1×**: `risk` already hands its findings to every impact
127
+ chain it composes (C3-42-b), so the duplication the field named is between two
128
+ *processes*, not inside one. That needs a persisted audit keyed on the worktree
129
+ signature, and it is not in this release. What ships is the regression test that
130
+ keeps the in-run property true.
131
+
132
+ ## [4.12.0] — 2026-08-08
133
+
134
+ **Field evaluation #16 — the third round on the same repository and commit, and the
135
+ first A/B/C of three of our own releases (4.10.6 → 4.10.7 → 4.11.0, 7,0 → 7,0 →
136
+ 6,5).** The three value axes did not move: correctness 9,5, unique value 9,0,
137
+ epistemic honesty 10, with `endpoints.json`, `servlets.json` and `validation.json`
138
+ **byte-identical across all three builds**. What fell is engineering, and the
139
+ sentence is about us: *"no es que haya regresiones — es que no hay nada que las
140
+ detecte."* Nine rows close here, one commit each.
141
+
142
+ **Behaviour changes to read before upgrading a pipeline:**
143
+
144
+ - **`risk_score` and `risk_score_raw` are `null`, not `0.0`, when the container
145
+ invokes the queried component** (`@Configuration`, `@Aspect`, a registered
146
+ filter). The band already said `high` for these while the number said `0.0` — the
147
+ worst possible value for the most critical symbol in a system, from the same
148
+ object. `risk_score_basis` says why and names the surfaces that do measure such a
149
+ component. Affects `impact-chain`, `impact`, `plan` and `compare`; a consumer
150
+ reading the field with a `0.0` default now sees `null`.
151
+ - **`ASK_MAX_ANALYSIS_SECONDS` actually stops a `spring-audit` rule pass.** It was
152
+ only ever checked between the three phases, and the last one holds ~85 % of the
153
+ time, so a 420 s budget produced a 2 351 s run and no `partial` anywhere. A cut
154
+ run now publishes `metadata.rules_not_run`, a `PARTIAL:` limitation, and
155
+ `_partial.phases_stopped_inside`. `metadata.rules_run` is present on every run.
156
+ - **`migrate-check` no longer creates `<scope>/.ask/` unless you pass `--snapshot`.**
157
+ A read-only run left a git-ignored directory in the audited repository — invisible
158
+ to `git status`, visible only to `git status --ignored`.
159
+ - **`verify` gains `--allow-unverified`** (exit 0 while the verdict stays
160
+ `unverified`) and publishes `unverified_reason`: `no_contracts_declared` or
161
+ `gate_could_not_run`. Exit codes are unchanged; the remedy is now the first reason.
162
+ - **The parse cache retires generations.** Entries live under the producing
163
+ analyzer's fingerprint, and a run purges the generations that can never be read
164
+ again — the field measured +142,53 MB per release, three copies of one repository,
165
+ 427,59 MB after three runs. `cache status` publishes the composition.
166
+
167
+ **Also in this release:**
168
+
169
+ - The rule pass reports the family it is on, counted against the total, and names
170
+ the two stages that have no population (`spring-audit` and `risk`). The ETA is
171
+ withheld there on purpose: rule families are not comparable units.
172
+ - `risk` checkpoints — after the audit and every 25 composed rows — with the partial
173
+ file built by the same assembler as the answer, so it is the same document over
174
+ fewer rows.
175
+ - The budget message stops quoting a class constant in the grammar of a
176
+ measurement. It carries this command's measured anchors and the size of the
177
+ repository in hand, and says plainly that no projection is offered. Anchors
178
+ recalibrated: `spring-audit` 408 s → 2 351 s at 3 342 files, `endpoints` 4,2 → 5,0,
179
+ `migrate-check --compact` 26,7 and `posture` 38,0 (both previously recorded as not
180
+ finishing), `risk` 3 232.
181
+ - The MCP stack's incomplete-model warning no longer reaches stderr, and the models
182
+ it complains about are rebuilt rather than only muted.
183
+ - A 772-line temp file (`cache.tmp_new`) was shipping inside the wheel; removed, and
184
+ the package contents are now asserted by allow-list.
185
+
186
+ **Measured and not fixed, deliberately:** the block performance regression the
187
+ evaluation reports (seven commands 2–3,4× slower on a minor) **does not reproduce
188
+ here** — the A/B of 4.10.7 against 4.11.0 on a 2 985-file repository lands within
189
+ 1 % on six commands, with `posture --resolve-environments` byte-identical, and
190
+ `risk` could not be A/B'd because the *4.10.7* side alone passed 61 minutes. Its
191
+ cost is structural, not new. The numbers are in the ledger; the row waits on a
192
+ Windows runner rather than on a guess.
193
+
5
194
  ## [4.11.0] — 2026-08-07
6
195
 
7
196
  **The queue behind Tier D: five ledger rows, and three batteries that were red in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.11.0
3
+ Version: 4.14.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.11.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.14.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`**
@@ -108,7 +108,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
108
108
  # pip / pipx
109
109
  pipx install sourcecode # or: pip install sourcecode
110
110
 
111
- ask version # ask 4.11.0 — and, on a build that has aged,
111
+ ask version # ask 4.14.0 — and, on a build that has aged,
112
112
  # how many releases have probably shipped since
113
113
  ```
114
114
 
@@ -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.11.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-4.14.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`**
@@ -70,7 +70,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
70
70
  # pip / pipx
71
71
  pipx install sourcecode # or: pip install sourcecode
72
72
 
73
- ask version # ask 4.11.0 — and, on a build that has aged,
73
+ ask version # ask 4.14.0 — and, on a build that has aged,
74
74
  # how many releases have probably shipped since
75
75
  ```
76
76
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.11.0"
7
+ version = "4.14.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"
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """Release performance gate (F-AP) — compare a run against a baseline, one verdict.
3
+
4
+ `scripts/perf_harness.py` measures; this decides. It reads two directories of
5
+ `perf-baseline-v1` cells, gates every cell it can match, and exits non-zero when
6
+ any of them regressed beyond the declared threshold, when a baselined cell was not
7
+ measured at all, or when a cell's sample is under the ADR-0006 floor.
8
+
9
+ The threshold, the repository, the mode and the excluded commands are constants in
10
+ `sourcecode.perf` — this script never carries a number of its own, and
11
+ `docs/perf/REGRESSION-GATE.md` is generated from the same constants.
12
+
13
+ Usage:
14
+ # gate a fresh run against the last release's baseline
15
+ python3 scripts/perf_gate.py \
16
+ --baseline docs/perf/baselines/4.12.0 \
17
+ --current docs/perf/baselines/4.13.0 \
18
+ --report gate.json --table RELEASE-TABLE.md
19
+
20
+ # prove the gate is armed: a synthetic 3x-slower cell must fail it
21
+ python3 scripts/perf_gate.py --self-test
22
+
23
+ # print the declared configuration (what the doc renders from)
24
+ python3 scripts/perf_gate.py --print-config
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import copy
31
+ import json
32
+ import sys
33
+ from pathlib import Path
34
+
35
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
36
+ from sourcecode import perf # noqa: E402
37
+
38
+
39
+ def load_cells(directory: Path) -> dict[str, dict]:
40
+ """Every `perf-baseline-v1` cell in *directory*, keyed by `<repo>__<cmd>__<mode>`.
41
+
42
+ Gate artifacts (`*.gate.json`) and anything that is not a cell are skipped
43
+ rather than mis-read: a directory that also holds a report must not turn the
44
+ report into a measurement.
45
+ """
46
+ cells: dict[str, dict] = {}
47
+ if not directory.is_dir():
48
+ return cells
49
+ for path in sorted(directory.glob("*.json")):
50
+ if path.name.endswith(".gate.json"):
51
+ continue
52
+ try:
53
+ cell = json.loads(path.read_text(encoding="utf-8"))
54
+ except (OSError, ValueError):
55
+ continue
56
+ if not isinstance(cell, dict) or cell.get("schema_version") != perf.SCHEMA_VERSION:
57
+ continue
58
+ cells[perf.cell_id(cell)] = cell
59
+ return cells
60
+
61
+
62
+ def _self_test() -> int:
63
+ """A deliberately slowed command must fail the gate. Run in CI before the gate.
64
+
65
+ This is the exit criterion stated as an executable check: it is not enough for
66
+ the gate to exist and be green — a green gate is only evidence when a red one
67
+ is reachable on the same machine, in the same job, with the same constants.
68
+ """
69
+ base = perf.build_baseline_cell(
70
+ tool_version="self-test",
71
+ command="spring-audit",
72
+ repo={"name": perf.RELEASE_GATE_REPO, "commit": "self-test", "files": 3000},
73
+ mode=perf.RELEASE_GATE_MODE,
74
+ wall_ms_runs=[1000.0] * perf.RELEASE_GATE_MIN_RUNS,
75
+ rss_bytes_runs=[1.0] * perf.RELEASE_GATE_MIN_RUNS,
76
+ )
77
+ slowed = copy.deepcopy(base)
78
+ slowed["wall_ms"] = {k: v * 3.0 for k, v in base["wall_ms"].items()}
79
+
80
+ cid = perf.cell_id(base)
81
+ red = perf.gate_report({cid: base}, {cid: slowed})
82
+ green = perf.gate_report({cid: base}, {cid: base})
83
+ dropped = perf.gate_report({cid: base}, {})
84
+
85
+ ok = (not red["passed"]) and green["passed"] and (not dropped["passed"])
86
+ print(f"self-test: 3x slower -> {'FAIL (correct)' if not red['passed'] else 'PASS (BROKEN)'}")
87
+ print(f"self-test: unchanged -> {'PASS (correct)' if green['passed'] else 'FAIL (BROKEN)'}")
88
+ print(f"self-test: cell gone -> {'FAIL (correct)' if not dropped['passed'] else 'PASS (BROKEN)'}")
89
+ if not ok:
90
+ print("perf gate self-test FAILED: the gate would not catch a regression.",
91
+ file=sys.stderr)
92
+ return 0 if ok else 1
93
+
94
+
95
+ def _print_config() -> int:
96
+ print(json.dumps({
97
+ "schema_version": perf.GATE_SCHEMA_VERSION,
98
+ "threshold": perf.RELEASE_GATE_THRESHOLD,
99
+ "repository": perf.RELEASE_GATE_REPO,
100
+ "repository_url": perf.RELEASE_GATE_REPO_URL,
101
+ "repository_scale": perf.RELEASE_GATE_REPO_SCALE,
102
+ "mode": perf.RELEASE_GATE_MODE,
103
+ "min_runs": perf.RELEASE_GATE_MIN_RUNS,
104
+ "commands": list(perf.RELEASE_GATE_COMMANDS),
105
+ "exclusions": [
106
+ {"command": c, "reason": r} for c, r in perf.RELEASE_GATE_EXCLUSIONS
107
+ ],
108
+ }, indent=2))
109
+ return 0
110
+
111
+
112
+ def main() -> int:
113
+ ap = argparse.ArgumentParser(
114
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
115
+ )
116
+ ap.add_argument("--baseline", type=Path, help="Directory of the release being compared against.")
117
+ ap.add_argument("--current", type=Path, help="Directory of this run's cells.")
118
+ ap.add_argument("--threshold", type=float, default=perf.RELEASE_GATE_THRESHOLD,
119
+ help=f"Max allowed regression ratio (declared default: "
120
+ f"{perf.RELEASE_GATE_THRESHOLD}).")
121
+ ap.add_argument("--min-runs", type=int, default=perf.RELEASE_GATE_MIN_RUNS)
122
+ ap.add_argument("--allow-incomparable", action="store_true",
123
+ help="Do not fail cells whose env/host differ. Off by default: "
124
+ "an incomparable cell is an unmeasured one.")
125
+ ap.add_argument("--report", type=Path, help="Write the perf-gate-v1 report here.")
126
+ ap.add_argument("--table", type=Path, help="Write the release-notes markdown table here.")
127
+ ap.add_argument("--self-test", action="store_true",
128
+ help="Prove the gate is armed; measures nothing.")
129
+ ap.add_argument("--print-config", action="store_true",
130
+ help="Print the declared gate configuration as JSON.")
131
+ args = ap.parse_args()
132
+
133
+ if args.self_test:
134
+ return _self_test()
135
+ if args.print_config:
136
+ return _print_config()
137
+ if not args.current:
138
+ ap.error("--current is required (or use --self-test / --print-config)")
139
+
140
+ current = load_cells(args.current)
141
+ if not current:
142
+ print(f"no perf cells in {args.current} — nothing was measured, "
143
+ f"which the gate reads as a failure, not a pass.", file=sys.stderr)
144
+ return 1
145
+ baseline = load_cells(args.baseline) if args.baseline else {}
146
+
147
+ report = perf.gate_report(
148
+ baseline, current,
149
+ max_regression=args.threshold,
150
+ allow_incomparable=args.allow_incomparable,
151
+ require_min_runs=args.min_runs,
152
+ )
153
+ table = perf.release_table(list(current.values()), gate=report)
154
+
155
+ if args.report:
156
+ args.report.parent.mkdir(parents=True, exist_ok=True)
157
+ args.report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
158
+ if args.table:
159
+ args.table.parent.mkdir(parents=True, exist_ok=True)
160
+ args.table.write_text(table, encoding="utf-8")
161
+
162
+ print(table)
163
+ if not baseline:
164
+ print("no baseline directory — cells recorded, nothing compared.", file=sys.stderr)
165
+ for cid in report["regressions"]:
166
+ print(f"REGRESSION: {cid}", file=sys.stderr)
167
+ for cid in report["missing_cells"]:
168
+ print(f"NOT MEASURED (baselined): {cid}", file=sys.stderr)
169
+ for cid in report["under_sampled_cells"]:
170
+ print(f"UNDER-SAMPLED: {cid}", file=sys.stderr)
171
+
172
+ return 0 if report["passed"] else 1
173
+
174
+
175
+ if __name__ == "__main__":
176
+ raise SystemExit(main())
@@ -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.11.0"
7
+ __version__ = "4.14.0"
@@ -30,12 +30,14 @@ the perf baselines do.
30
30
  from __future__ import annotations
31
31
 
32
32
  import json
33
+ import os
33
34
  import subprocess
34
35
  from datetime import datetime, timezone
35
36
  from pathlib import Path
36
37
  from typing import TYPE_CHECKING, Any
37
38
 
38
39
  from sourcecode import __version__ as TOOL_VERSION
40
+ from sourcecode import readonly as _readonly
39
41
  from sourcecode.architectural_delta import ArchMetrics, diff_metrics, extract_metrics
40
42
  from sourcecode.perf import collect_env
41
43
 
@@ -365,21 +367,40 @@ def _baseline_filename(baseline: dict) -> str:
365
367
  #: a 14 587-entry structural inventory of the code. A self-confined
366
368
  #: `.ask/.gitignore` is the honest form: it never touches the user's own
367
369
  #: `.gitignore`, and it stops applying the moment they delete the directory.
370
+ #: C3-74 corrected the last line. It used to read *"Delete this file to version
371
+ #: them deliberately"*, and deleting it is the one action that would commit a
372
+ #: baseline carrying `env.hostname_hash`, `cpu` and `cores` — an invitation to do
373
+ #: the thing the file exists to prevent. It now states the consequence instead.
368
374
  _ASK_DIR_GITIGNORE = (
369
375
  "# Written by `ask` the first time it created this directory.\n"
370
376
  "# These are local analysis artefacts: a structural inventory of your code\n"
371
377
  "# and, if you asked for it, the machine that measured it. Nothing here\n"
372
- "# belongs in a commit. Delete this file to version them deliberately.\n"
378
+ "# belongs in a commit.\n"
379
+ "#\n"
380
+ "# Removing this file makes everything here committable, including any\n"
381
+ "# recorded machine fingerprint (`--record-env`: hostname hash, CPU, cores).\n"
382
+ "# Version these deliberately only if you intend to publish that.\n"
373
383
  "*\n"
374
384
  )
375
385
 
376
386
 
377
- def protect_ask_dir(out_dir: Path) -> None:
387
+ def protect_ask_dir(out_dir: Path, *, announce: bool = True) -> None:
378
388
  """Give a freshly created `.ask/` its own `.gitignore` (C3-68).
379
389
 
380
390
  Best-effort and never overwriting: a user who edited it decided something,
381
391
  and a tool that re-imposes its default on every run is worse than one that
382
392
  never wrote it.
393
+
394
+ **Call this where the write happens, never where the path is computed.** This
395
+ function *creates* the directory in order to protect it, so calling it
396
+ speculatively is a write — which is how `migrate-check` came to leave a
397
+ `.ask/.gitignore` inside a repository it had only read (C3-74).
398
+
399
+ `announce` puts one line on stderr the first time the directory is created,
400
+ with its path. A side effect inside somebody else's repository is exactly what
401
+ I-8 says must be declared, and this one hides from the check an auditor makes:
402
+ `git status --porcelain` is clean because the ignore file ignores itself, and
403
+ only `--ignored` reveals `!! <scope>/.ask/`.
383
404
  """
384
405
  try:
385
406
  ask_dir = Path(out_dir)
@@ -391,14 +412,41 @@ def protect_ask_dir(out_dir: Path) -> None:
391
412
  return
392
413
  marker = ask_dir / ".gitignore"
393
414
  if not marker.exists():
415
+ created = not ask_dir.exists()
416
+ # F-AS: the guard raises an OSError, which the `except OSError` below
417
+ # turns into "did not write" — the mode's promise, no new branch.
418
+ _readonly.guard(ask_dir, what="this tool's own directory")
394
419
  ask_dir.mkdir(parents=True, exist_ok=True)
395
420
  marker.write_text(_ASK_DIR_GITIGNORE, encoding="utf-8")
421
+ if announce and created:
422
+ _announce_ask_dir(ask_dir)
396
423
  except OSError:
397
424
  return
398
425
 
399
426
 
427
+ def _announce_ask_dir(ask_dir: Path) -> None:
428
+ """One line, on stderr, naming what was created and how to move it (C3-74)."""
429
+ import sys
430
+
431
+ try:
432
+ # No flag is named here on purpose: the commands that write under `.ask/`
433
+ # spell the override differently (`baseline capture --dir`,
434
+ # `migrate-check --history-dir`), and a message that names the wrong one
435
+ # is the class of defect this ledger is about.
436
+ sys.stderr.write(
437
+ f"[ask] created {ask_dir}{os.sep} for this run's local artefacts — "
438
+ f"git-ignored by its own .gitignore, so `git status` will not show it "
439
+ f"(`git status --ignored` will). The command's directory option takes a "
440
+ f"path outside the repository if you want them elsewhere.\n"
441
+ )
442
+ sys.stderr.flush()
443
+ except Exception:
444
+ pass
445
+
446
+
400
447
  def write_baseline(baseline: dict, out_dir: Path) -> Path:
401
448
  """Write `baseline` as deterministic JSON under `out_dir`; return the file path."""
449
+ _readonly.guard(out_dir, what="baseline history directory")
402
450
  out_dir.mkdir(parents=True, exist_ok=True)
403
451
  protect_ask_dir(out_dir)
404
452
  path = out_dir / _baseline_filename(baseline)