sourcecode 5.0.1__tar.gz → 5.2.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.
- {sourcecode-5.0.1 → sourcecode-5.2.0}/.github/workflows/perf-gate.yml +14 -2
- {sourcecode-5.0.1 → sourcecode-5.2.0}/CHANGELOG.md +167 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/PKG-INFO +3 -3
- {sourcecode-5.0.1 → sourcecode-5.2.0}/README.md +2 -2
- {sourcecode-5.0.1 → sourcecode-5.2.0}/pyproject.toml +1 -1
- {sourcecode-5.0.1 → sourcecode-5.2.0}/scripts/perf_gate.py +27 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/scripts/perf_harness.py +60 -8
- sourcecode-5.2.0/scripts/sync_gate_anchors.py +144 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/architectural_delta.py +57 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/cache_model.py +212 -29
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/cli.py +390 -58
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/contract_diff.py +27 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/execution_plan.py +41 -2
- sourcecode-5.2.0/src/sourcecode/gate_anchors.py +42 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/parse_cache.py +43 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/perf.py +168 -16
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/verify_edit.py +123 -16
- {sourcecode-5.0.1 → sourcecode-5.2.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/.gitignore +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/.ruff.toml +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/CLAUDE.md +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/CONTRIBUTING.md +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/LICENSE +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/SECURITY.md +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/raw +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/container_wiring.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/declarations.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detach.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/license.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/dependency_import.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/output_encoding.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/parallel.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/partial_contract.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/phased_run.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/posture.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/provenance.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/readonly.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/release_info.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/remedies.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/risk.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/rule_pass.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/runs.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/sarif.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/security_chain.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/security_config_scan.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/source_text.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/text_input.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/functions/README.md +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-5.0.1 → sourcecode-5.2.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -7,7 +7,14 @@ name: Performance regression gate
|
|
|
7
7
|
# population are declared in `sourcecode.perf` and documented in
|
|
8
8
|
# `docs/perf/REGRESSION-GATE.md`; nothing here carries a number of its own.
|
|
9
9
|
|
|
10
|
+
# C3-110: for five releases this ran nightly and on demand, and nothing in the
|
|
11
|
+
# release path asked it anything - so a release could regress and ship, and the
|
|
12
|
+
# nightly would report it to nobody at 03:00. A tag is a release, and a release
|
|
13
|
+
# is gated.
|
|
10
14
|
on:
|
|
15
|
+
push:
|
|
16
|
+
tags:
|
|
17
|
+
- "v*"
|
|
11
18
|
schedule:
|
|
12
19
|
# Nightly, after the day's merges have settled.
|
|
13
20
|
- cron: "0 3 * * *"
|
|
@@ -60,7 +67,7 @@ jobs:
|
|
|
60
67
|
echo "repo_url=$(python3 -c "import json;print(json.load(open('gate-config.json'))['repository_url'])")" >> "$GITHUB_OUTPUT"
|
|
61
68
|
echo "repo=$(python3 -c "import json;print(json.load(open('gate-config.json'))['repository'])")" >> "$GITHUB_OUTPUT"
|
|
62
69
|
echo "fleet_subdir=$(python3 -c "import json;print(json.load(open('gate-config.json'))['fleet_subdir'])")" >> "$GITHUB_OUTPUT"
|
|
63
|
-
echo "
|
|
70
|
+
echo "modes=$(python3 -c "import json;print(' '.join(json.load(open('gate-config.json'))['modes']))")" >> "$GITHUB_OUTPUT"
|
|
64
71
|
echo "runs=$(python3 -c "import json;print(json.load(open('gate-config.json'))['min_runs'])")" >> "$GITHUB_OUTPUT"
|
|
65
72
|
echo "commands=$(python3 -c "import json;print(' '.join(repr(c) for c in json.load(open('gate-config.json'))['commands']))")" >> "$GITHUB_OUTPUT"
|
|
66
73
|
|
|
@@ -79,16 +86,21 @@ jobs:
|
|
|
79
86
|
python3 scripts/perf_harness.py \
|
|
80
87
|
--repos "${{ steps.cfg.outputs.repo }}" \
|
|
81
88
|
--commands ${{ steps.cfg.outputs.commands }} \
|
|
82
|
-
--modes
|
|
89
|
+
--modes ${{ steps.cfg.outputs.modes }} \
|
|
83
90
|
--runs "${{ steps.cfg.outputs.runs }}" \
|
|
84
91
|
--fleet-root fleet \
|
|
85
92
|
--out perf-run
|
|
86
93
|
|
|
87
94
|
- name: Gate
|
|
95
|
+
# Two comparisons, one verdict. `--baseline` is the previous release and
|
|
96
|
+
# decides; `--drift-baseline` is the best build ever measured and is
|
|
97
|
+
# published - a staircase of steps each under the threshold is invisible
|
|
98
|
+
# to the first and visible to the second (C3-110).
|
|
88
99
|
run: |
|
|
89
100
|
python3 scripts/perf_gate.py \
|
|
90
101
|
--baseline "$BASELINE_DIR" \
|
|
91
102
|
--require-baseline \
|
|
103
|
+
--drift-baseline "docs/perf/baselines/$(python3 -c "import json;print(json.load(open('gate-config.json'))['drift_baseline'])")" \
|
|
92
104
|
--current perf-run \
|
|
93
105
|
--report perf-run/gate.json \
|
|
94
106
|
--table perf-run/RELEASE-TABLE.md
|
|
@@ -2,6 +2,173 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.2.0] — 2026-08-11
|
|
6
|
+
|
|
7
|
+
**The queue field evaluation #26 opened, and the row underneath the other rows.**
|
|
8
|
+
The eleventh round retested 5.1.0 on the same subject with the same total purge,
|
|
9
|
+
scored 8,4/10, and printed the series that names the real defect: `validation` on
|
|
10
|
+
one commit of one repository, across eleven releases —
|
|
11
|
+
`35,4 → 14,0 → 36,8 → 35,8 → 41,8 → 17,8 → 39,8 → 13,8 → 34,1 → 39,5 s`. Four
|
|
12
|
+
oscillations of ~2,9×, no net direction, and a green gate throughout.
|
|
13
|
+
|
|
14
|
+
No analysis changes: what moves is what the product does when there is nothing to
|
|
15
|
+
diff, when two states are one state, when it writes an IR to a file, and when it
|
|
16
|
+
tells you what a command costs.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **The release is gated, over the population the field measures (C3-110).** The
|
|
21
|
+
detector has existed since 4.13.0 and had never blocked a release: nothing in
|
|
22
|
+
the release path asked it anything (`push: tags: v*` does now; the nightly
|
|
23
|
+
stays, to catch a regression the day it lands rather than the day it ships);
|
|
24
|
+
its population was six commands against the eighteen the field times (`risk`,
|
|
25
|
+
`modernize`, `repo-ir` and `data-exposure` are gated now, and every exclusion
|
|
26
|
+
carries its reason); and it compared against the previous release alone, which
|
|
27
|
+
passes every step of a staircase — `drift_report()` publishes the distance from
|
|
28
|
+
the best build this product has measured, without deciding, because some of that
|
|
29
|
+
distance is work deliberately added and the comparison cannot tell which. Two
|
|
30
|
+
defects found while measuring the gate itself: **cold cells were not cold**
|
|
31
|
+
(only the snapshot layer was isolated, so every cold run inherited the previous
|
|
32
|
+
cell's parse store — the exact regime four in-house A/Bs failed to reproduce a
|
|
33
|
+
field regression in), and **a failed run was recorded as a fast one**
|
|
34
|
+
(`--no-cache` is rejected by most subcommands, so `ask risk . --no-cache` exited
|
|
35
|
+
2 and the harness timed the error envelope at ~0,29 s on any repository).
|
|
36
|
+
- **A figure that advises names the build it was measured on (C3-110/C3-109).**
|
|
37
|
+
`spring-audit` published *"8,4 s (4.18.0)"* while the same command measured
|
|
38
|
+
31,0 s on the build in hand, and `risk` carried *"not a foreground run here — a
|
|
39
|
+
nightly job"* off a 73,3 s anchor from another build while the field ran it in
|
|
40
|
+
the foreground in 77,2 s. Advising surfaces now say the build **and** that this
|
|
41
|
+
build has not been re-measured, and *"not a foreground run"* is reserved for
|
|
42
|
+
figures above 120 s: between the thresholds a command is offered with its cost
|
|
43
|
+
attached. The anchors are refreshed from evaluation #26.
|
|
44
|
+
- **A tree with nothing to diff is not analysed (C3-102).** `verify-edit` spent
|
|
45
|
+
119,5 s on the field's repository to produce 807 bytes saying the tree is clean.
|
|
46
|
+
Every axis diffs the two models and on a clean tree they are one object, so no
|
|
47
|
+
analysis can change the answer: the clean run takes a model already cached for
|
|
48
|
+
that tree state and builds nothing when there is none. The verdict publishes
|
|
49
|
+
what it read, including the claim it does not make — `pass` means the working
|
|
50
|
+
tree equals HEAD, not that HEAD was audited. **9,94 s → 0,62 s** on a clean
|
|
51
|
+
worktree of BroadleafCommerce, every layer purged.
|
|
52
|
+
- **One tree state is not analysed at all (C3-107).** 5.1.0 stopped the second
|
|
53
|
+
build; this stops the first. `delta` **8,08 → 0,35 s** and `contract-diff`
|
|
54
|
+
**13,69 → 0,33 s** on the same subject, with every delta published as the zero
|
|
55
|
+
determinism proves and every per-side total as `null` — never 0, because an axis
|
|
56
|
+
nobody measured is unknown. `contract-diff` also states what `unchanged` means:
|
|
57
|
+
the difference between the two states, not the health of the contract in either.
|
|
58
|
+
- **The written IR has a ceiling, and the refusal reaches a script (C3-111).** The
|
|
59
|
+
stdout guard was real and the `-o` path had no limit at all, while the pre-build
|
|
60
|
+
size band travels through a notice that is silent off-TTY — so the run that
|
|
61
|
+
writes the file is the one that hears nothing: 101 254 852 B, byte-identical
|
|
62
|
+
across three releases. An estimate above 50 MB now refuses **before the
|
|
63
|
+
analysis**, through the error envelope, naming the band, its basis and the four
|
|
64
|
+
ways out that already existed (`--summary-only`, `--max-nodes/--max-edges`,
|
|
65
|
+
`--gzip`, `--force`).
|
|
66
|
+
|
|
67
|
+
### Measured, not fixed
|
|
68
|
+
|
|
69
|
+
- **C3-103 (two root views of one state).** On a purged BroadleafCommerce
|
|
70
|
+
worktree: `--compact --git-context --env-map` 14,1 s, `--agent` after it 20,5 s,
|
|
71
|
+
`--agent` alone 25,3 s. The analysis difference between the two cores is exactly
|
|
72
|
+
one flag (`graph_modules`), so the increment is augmenting a cached core with one
|
|
73
|
+
analyzer — with byte-identity against a cold `--agent` run as its acceptance
|
|
74
|
+
test. A core that is *almost* the cold one is the C3-94 class of defect, so it is
|
|
75
|
+
not attempted under a deadline.
|
|
76
|
+
|
|
77
|
+
## [5.1.0] — 2026-08-11
|
|
78
|
+
|
|
79
|
+
**The operational queue field evaluation #25 left open, and the one row it opened
|
|
80
|
+
that this project can act on.** No analysis changes: `endpoints`, `validation`,
|
|
81
|
+
`posture`, `spring-audit` and `migrate-check` were re-run on spring-petclinic,
|
|
82
|
+
jobrunr and BroadleafCommerce against 5.0.1 from a purged cache, and every
|
|
83
|
+
payload matches byte for byte except the fields that are clocks by definition
|
|
84
|
+
(`generated_at`, `analysis_time_ms`). What moves is what the product does when
|
|
85
|
+
you clear a cache, compare a tree with itself, ask it whether you have edited
|
|
86
|
+
anything, or read what a command costs — and `delta` / `contract-diff` gain one
|
|
87
|
+
declared key, `comparison.identical_states`.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **A confirmation prompt that cannot be answered is diagnosed, not raised
|
|
92
|
+
(C3-106).** v1.45.0 guarded the case it knew about — stdin that is not a
|
|
93
|
+
terminal — and the field found the one it does not cover: a PowerShell 5.1
|
|
94
|
+
session driven by a harness where `isatty()` answers true and the read hits
|
|
95
|
+
end-of-input at once, producing a Python traceback under a half-printed prompt.
|
|
96
|
+
All four prompts (`cache clear`, `cache context-clear`, `mcp init`,
|
|
97
|
+
`mcp remove` — the last two had no guard at all) now emit the product's own
|
|
98
|
+
envelope: `INVALID_INPUT`, the command that asked, `reason:
|
|
99
|
+
unanswerable_prompt`, and the `--yes` flag that removes the question. Exit 2,
|
|
100
|
+
because nothing was done. A plain "no" is still an answer: exit 1.
|
|
101
|
+
- **The shared parse store can be cleared, and says so (C3-104).** `cache status`
|
|
102
|
+
publishes three layers and `cache clear` emptied two; the third — the per-file
|
|
103
|
+
parse store — had no eviction surface at all, so a from-scratch audit meant
|
|
104
|
+
`rm -rf ~/.sourcecode/parse-cache-v1`, a path the field had to learn from our
|
|
105
|
+
documentation. `ask cache clear --global` empties it and reports what it
|
|
106
|
+
removed; the default run names the layer, its size and the flag. Global on
|
|
107
|
+
purpose: the store is content-addressed across repositories, so "this
|
|
108
|
+
repository's entries" is not a set it can produce.
|
|
109
|
+
- **One tree state is read once (C3-107).** `delta` and `contract-diff` built both
|
|
110
|
+
sides and then asked whether they differ. Identity is decided first — the
|
|
111
|
+
commit on the ref path, `cache.worktree_signature` on the checkout path, and
|
|
112
|
+
only when both sides produced a signature — so identical refs are materialised
|
|
113
|
+
once and the head IR is the base one. `comparison.identical_states` is always
|
|
114
|
+
present, with its basis, so a consumer can tell a degenerate comparison from a
|
|
115
|
+
real one. Wall time does not move on macOS (9,28 → 9,21 s on BroadleafCommerce):
|
|
116
|
+
the caches already served the second side, and what this removes is work whose
|
|
117
|
+
result the product already held.
|
|
118
|
+
- **A clean tree is not checked out a second time (C3-102).** `verify-edit` reads
|
|
119
|
+
the shared entry instead of materialising HEAD in a throwaway worktree when git
|
|
120
|
+
reports the tree unmodified, and writes what it builds into both the shared and
|
|
121
|
+
the HEAD-sha lanes, so the first edit after a clean run is still warm. The
|
|
122
|
+
measured gain is small (10,32 → 10,03 s cold on BroadleafCommerce): the field's
|
|
123
|
+
76,6 s is one build of a 3 342-file repository, not a duplicated one. What
|
|
124
|
+
changes is that the build is no longer written where only `verify-edit` can
|
|
125
|
+
read it, and a clean-tree run no longer writes `.git/worktrees` in your
|
|
126
|
+
repository.
|
|
127
|
+
- **The `repo-ir` size band contains the largest repository ever measured
|
|
128
|
+
(C3-105).** The band was 10,8-27,9 KB/file and the field measured 30,3
|
|
129
|
+
(101 254 852 B over 3 342 files) — the one number offered for planning excluded
|
|
130
|
+
the measurement a reader would take. The fifth sample is admitted, and the band
|
|
131
|
+
now publishes its sample size and its direction: the top under-states, because
|
|
132
|
+
the sample that set it counted tests among its files and the estimate does not.
|
|
133
|
+
|
|
134
|
+
### Added
|
|
135
|
+
|
|
136
|
+
- **A cached core richer than the requested view now serves it (C3-103).** The
|
|
137
|
+
overlay lookup already reused a *poorer* base and re-attached what it flipped;
|
|
138
|
+
the reverse was never tried, so asking for less than what was on disk paid for
|
|
139
|
+
a full analysis. The candidate set is symmetric now, with the dropped blocks
|
|
140
|
+
removed from the view so a caller never receives something it did not ask for.
|
|
141
|
+
On jobrunr: `--compact --git-context` then `--compact`, 1,15 s → **0,32 s**,
|
|
142
|
+
payload equal to a cold run block for block.
|
|
143
|
+
- **Every build carries an in-house measurement of itself (C3-109).** The field
|
|
144
|
+
anchors are the field's, on their repository and their machine, so this project
|
|
145
|
+
can never refresh them — which is why B17 reopened one release after it closed:
|
|
146
|
+
labelling a figure does not re-measure it. `ask cache model` now prints the
|
|
147
|
+
release gate's own figure beside the field's (`gate:`, `gate_measurement`,
|
|
148
|
+
`gate_currency()`), with its scale, cache state, host class and build. Beside,
|
|
149
|
+
never instead — and when the in-house one is behind there is a command to fix
|
|
150
|
+
it rather than an evaluation to wait for. Generated from the committed baseline
|
|
151
|
+
by `scripts/sync_gate_anchors.py`; the suite fails if the two disagree.
|
|
152
|
+
- **The gate measures the cold regime (C3-108).** Four consecutive in-house A/Bs
|
|
153
|
+
have failed to reproduce a field-reported regression, and the gate measured
|
|
154
|
+
warm only — while every field round since #23 purges every layer first. Cold
|
|
155
|
+
cells are captured and published (`observed_not_gated`, `decides: false` per
|
|
156
|
+
cell) and still may not block a release, because a cold median on an ephemeral
|
|
157
|
+
runner carries a cold page cache as well as a cold parse store.
|
|
158
|
+
|
|
159
|
+
### Not fixed, and why
|
|
160
|
+
|
|
161
|
+
- **The 2-2,6x regression across sixteen commands reported between 4.18.0 and
|
|
162
|
+
5.0.1 (C3-108) is open and unattributed.** Measured here on the field's own
|
|
163
|
+
method — every layer purged before each run — three builds on BroadleafCommerce
|
|
164
|
+
land within 2 % (`endpoints` 4,96 / 4,19 / 4,19 s · `spring-audit` 9,87 / 9,76 /
|
|
165
|
+
9,75 s · `repo-ir` 4,20 / 4,12 / 4,21 s · `validation` 9,39 / 9,26 / 9,33 s for
|
|
166
|
+
5.0.0 / 4.18.0 / 5.0.1), and the ADR-0006 harness on openmrs-core lands between
|
|
167
|
+
-0,6 % and +1,8 %, inside the ±5 % noise floor. The 4.18.0→5.0.1 diff is 13
|
|
168
|
+
files and 702 insertions, two of them on an analysis path and neither adding a
|
|
169
|
+
pass. A 2x a customer can measure is high whatever its cause, so the row stays
|
|
170
|
+
open: what it needs is a same-session A/B on the machine that sees it.
|
|
171
|
+
|
|
5
172
|
## [5.0.1] — 2026-08-11
|
|
6
173
|
|
|
7
174
|
**A patch on the release that made the product's own cost figures honest, for the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 5.0
|
|
3
|
+
Version: 5.2.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
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
126
126
|
# pip / pipx
|
|
127
127
|
pipx install sourcecode # or: pip install sourcecode
|
|
128
128
|
|
|
129
|
-
ask version # ask 5.0
|
|
129
|
+
ask version # ask 5.2.0 — and, on a build that has aged,
|
|
130
130
|
# how many releases have probably shipped since
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|

|
|
9
9
|
|
|
10
10
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
88
88
|
# pip / pipx
|
|
89
89
|
pipx install sourcecode # or: pip install sourcecode
|
|
90
90
|
|
|
91
|
-
ask version # ask 5.0
|
|
91
|
+
ask version # ask 5.2.0 — and, on a build that has aged,
|
|
92
92
|
# how many releases have probably shipped since
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -108,8 +108,17 @@ def _print_config() -> int:
|
|
|
108
108
|
"fleet_subdir": FLEET.get(perf.RELEASE_GATE_REPO, perf.RELEASE_GATE_REPO),
|
|
109
109
|
"repository_scale": perf.RELEASE_GATE_REPO_SCALE,
|
|
110
110
|
"mode": perf.RELEASE_GATE_MODE,
|
|
111
|
+
# C3-108: what is measured and what decides are two lists now. The
|
|
112
|
+
# workflow reads `modes` for the harness; the gate reads the other.
|
|
113
|
+
"modes": list(perf.RELEASE_GATE_MEASURED_MODES),
|
|
114
|
+
"deciding_modes": list(perf.RELEASE_GATE_DECIDING_MODES),
|
|
111
115
|
"min_runs": perf.RELEASE_GATE_MIN_RUNS,
|
|
112
116
|
"commands": list(perf.RELEASE_GATE_COMMANDS),
|
|
117
|
+
# C3-110: what a command needs to be measurable at all, in one place the
|
|
118
|
+
# workflow reads instead of typing flags of its own.
|
|
119
|
+
"command_args": {k: list(v) for k, v in sorted(perf.RELEASE_GATE_COMMAND_ARGS.items())},
|
|
120
|
+
"drift_baseline": perf.RELEASE_GATE_DRIFT_BASELINE,
|
|
121
|
+
"drift_threshold": perf.RELEASE_GATE_DRIFT_THRESHOLD,
|
|
113
122
|
"exclusions": [
|
|
114
123
|
{"command": c, "reason": r} for c, r in perf.RELEASE_GATE_EXCLUSIONS
|
|
115
124
|
],
|
|
@@ -135,6 +144,9 @@ def main() -> int:
|
|
|
135
144
|
"to compare against records every cell as `new` and passes; "
|
|
136
145
|
"that is a recording, not a gate, and CI must not read it "
|
|
137
146
|
"as one.")
|
|
147
|
+
ap.add_argument("--drift-baseline", type=Path,
|
|
148
|
+
help="Directory of the best measured build. Compared and "
|
|
149
|
+
"published; never fails the run (C3-110).")
|
|
138
150
|
ap.add_argument("--report", type=Path, help="Write the perf-gate-v1 report here.")
|
|
139
151
|
ap.add_argument("--table", type=Path, help="Write the release-notes markdown table here.")
|
|
140
152
|
ap.add_argument("--self-test", action="store_true",
|
|
@@ -169,6 +181,16 @@ def main() -> int:
|
|
|
169
181
|
allow_incomparable=args.allow_incomparable,
|
|
170
182
|
require_min_runs=args.min_runs,
|
|
171
183
|
)
|
|
184
|
+
drift = None
|
|
185
|
+
if args.drift_baseline:
|
|
186
|
+
drift_cells = load_cells(args.drift_baseline)
|
|
187
|
+
if drift_cells:
|
|
188
|
+
drift = perf.drift_report(drift_cells, current)
|
|
189
|
+
report["drift"] = drift
|
|
190
|
+
else:
|
|
191
|
+
print(f"no drift-baseline cells in {args.drift_baseline} — the "
|
|
192
|
+
f"distance from the best measured build was not computed.",
|
|
193
|
+
file=sys.stderr)
|
|
172
194
|
table = perf.release_table(list(current.values()), gate=report)
|
|
173
195
|
|
|
174
196
|
if args.report:
|
|
@@ -187,6 +209,11 @@ def main() -> int:
|
|
|
187
209
|
print(f"NOT MEASURED (baselined): {cid}", file=sys.stderr)
|
|
188
210
|
for cid in report["under_sampled_cells"]:
|
|
189
211
|
print(f"UNDER-SAMPLED: {cid}", file=sys.stderr)
|
|
212
|
+
if drift:
|
|
213
|
+
for entry in drift["cells"]:
|
|
214
|
+
if entry.get("status") == "drifted":
|
|
215
|
+
print(f"DRIFT vs {drift['reference']}: {entry['cell']} "
|
|
216
|
+
f"x{entry['ratio']}", file=sys.stderr)
|
|
190
217
|
|
|
191
218
|
return 0 if report["passed"] else 1
|
|
192
219
|
|
|
@@ -118,19 +118,44 @@ def build_argv(command: str, repo_path: Path, cold: bool) -> list[str]:
|
|
|
118
118
|
argv.append(command)
|
|
119
119
|
argv.append(str(repo_path))
|
|
120
120
|
argv += ["--format", "json"]
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
# C3-110: some commands need an argument to be measurable at all, and the one
|
|
122
|
+
# authority for which is `perf`, not this script and not a workflow.
|
|
123
|
+
argv += list(perf.RELEASE_GATE_COMMAND_ARGS.get(command, ()))
|
|
124
|
+
# C3-110: `--no-cache` used to be appended here for cold cells, and most
|
|
125
|
+
# subcommands **reject** it — `ask risk <repo> --no-cache` exits 2 with
|
|
126
|
+
# INVALID_INPUT — so the cold cell for every one of them timed an error
|
|
127
|
+
# envelope (~0,29 s on any repository, any size). Cold is now what the field
|
|
128
|
+
# means by cold: both cache trees emptied before the run (see `measure_cell`),
|
|
129
|
+
# which needs no flag and works for every command in the population.
|
|
123
130
|
return argv
|
|
124
131
|
|
|
125
132
|
|
|
133
|
+
class HarnessRunFailed(RuntimeError):
|
|
134
|
+
"""A measured invocation did not succeed, so it is not a measurement (C3-110).
|
|
135
|
+
|
|
136
|
+
The gate's whole claim is that a number came from the command doing its work.
|
|
137
|
+
A run that exits non-zero produced an error envelope in a third of a second on
|
|
138
|
+
a repository of any size, and recording that as `wall_ms` is not a slow gate —
|
|
139
|
+
it is a gate reporting green on a command it never ran.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
|
|
126
143
|
def run_once(argv: list[str], env: dict[str, str], perf_log: Path) -> tuple[float, float, dict | None]:
|
|
127
|
-
"""Run the CLI once. Returns (wall_ms, rss_bytes, phase_ms | None).
|
|
144
|
+
"""Run the CLI once. Returns (wall_ms, rss_bytes, phase_ms | None).
|
|
145
|
+
|
|
146
|
+
Raises :class:`HarnessRunFailed` on a non-zero exit: see the class docstring.
|
|
147
|
+
"""
|
|
128
148
|
if perf_log.exists():
|
|
129
149
|
perf_log.unlink()
|
|
130
150
|
rss_before = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
|
|
131
151
|
t0 = time.perf_counter()
|
|
132
|
-
subprocess.run(argv, env=env, capture_output=True, timeout=1800)
|
|
152
|
+
proc = subprocess.run(argv, env=env, capture_output=True, timeout=1800)
|
|
133
153
|
wall_ms = (time.perf_counter() - t0) * 1000.0
|
|
154
|
+
if proc.returncode != 0:
|
|
155
|
+
detail = (proc.stderr or b"")[:400].decode("utf-8", "replace").strip()
|
|
156
|
+
raise HarnessRunFailed(
|
|
157
|
+
f"exit {proc.returncode} from `{' '.join(argv)}`: {detail or 'no stderr'}"
|
|
158
|
+
)
|
|
134
159
|
rss_after = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
|
|
135
160
|
# ru_maxrss for RUSAGE_CHILDREN is the peak among reaped children; the delta
|
|
136
161
|
# approximates this run's peak. Approximate by construction — documented.
|
|
@@ -156,12 +181,21 @@ def measure_cell(command: str, repo_path: Path, mode: str, runs: int) -> dict:
|
|
|
156
181
|
phases: list[dict] = []
|
|
157
182
|
|
|
158
183
|
with tempfile.TemporaryDirectory(prefix="perf-cache-") as cache_dir, \
|
|
184
|
+
tempfile.TemporaryDirectory(prefix="perf-ctx-") as ctx_dir, \
|
|
159
185
|
tempfile.TemporaryDirectory(prefix="perf-log-") as log_dir:
|
|
160
186
|
perf_log = Path(log_dir) / "phases.jsonl"
|
|
161
187
|
base_env = dict(os.environ)
|
|
162
188
|
base_env[perf.ENV_ENABLE] = "1"
|
|
163
189
|
base_env[perf.ENV_LOG] = str(perf_log)
|
|
164
190
|
base_env["SOURCECODE_CACHE_DIR"] = cache_dir
|
|
191
|
+
# **C3-110.** `SOURCECODE_CACHE_DIR` isolates the snapshot/view layer and
|
|
192
|
+
# nothing else: the shared CIR and the content-addressed parse store live
|
|
193
|
+
# under `SOURCECODE_CONTEXT_CACHE_DIR` (default `~/.sourcecode`), so every
|
|
194
|
+
# "cold" cell ran with a parse cache the previous cell had filled. The
|
|
195
|
+
# cold regime C3-108 added to the gate is the regime every field report
|
|
196
|
+
# since #23 measures — after purging exactly these two trees — and it was
|
|
197
|
+
# the one regime the harness was not measuring.
|
|
198
|
+
base_env["SOURCECODE_CONTEXT_CACHE_DIR"] = ctx_dir
|
|
165
199
|
|
|
166
200
|
# Warm mode: one un-recorded priming run to populate the cache, then the
|
|
167
201
|
# first measured warm run is still discarded (belt and suspenders).
|
|
@@ -169,9 +203,10 @@ def measure_cell(command: str, repo_path: Path, mode: str, runs: int) -> dict:
|
|
|
169
203
|
for i in range(n):
|
|
170
204
|
argv = build_argv(command, repo_path, cold=cold)
|
|
171
205
|
if cold:
|
|
172
|
-
# Each cold run gets
|
|
173
|
-
for
|
|
174
|
-
|
|
206
|
+
# Each cold run gets pristine cache dirs — both of them (C3-110).
|
|
207
|
+
for base in (cache_dir, ctx_dir):
|
|
208
|
+
for child in Path(base).iterdir():
|
|
209
|
+
shutil.rmtree(child, ignore_errors=True) if child.is_dir() else child.unlink()
|
|
175
210
|
wall, rss, phase = run_once(argv, base_env, perf_log)
|
|
176
211
|
if mode == "warm" and i == 0:
|
|
177
212
|
continue # discard cache-fill run
|
|
@@ -234,6 +269,7 @@ def main() -> int:
|
|
|
234
269
|
|
|
235
270
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
236
271
|
failures: list[tuple[str, dict]] = []
|
|
272
|
+
not_measured: list[str] = []
|
|
237
273
|
for cmd, name, mode in plan:
|
|
238
274
|
subdir = FLEET.get(name, name)
|
|
239
275
|
repo_path = args.fleet_root / subdir
|
|
@@ -245,6 +281,15 @@ def main() -> int:
|
|
|
245
281
|
cell = measure_cell(cmd, repo_path, mode, args.runs)
|
|
246
282
|
except subprocess.TimeoutExpired:
|
|
247
283
|
print(" TIMEOUT", file=sys.stderr)
|
|
284
|
+
not_measured.append(f"{name}__{cmd or 'ask'}__{mode}: timeout")
|
|
285
|
+
continue
|
|
286
|
+
except HarnessRunFailed as exc:
|
|
287
|
+
# C3-110: a cell that did not run is not written, so the gate reads it
|
|
288
|
+
# as `missing` — which it already fails on — instead of blessing an
|
|
289
|
+
# error envelope as a fast command.
|
|
290
|
+
print(" FAILED", file=sys.stderr)
|
|
291
|
+
print(f" {exc}", file=sys.stderr)
|
|
292
|
+
not_measured.append(f"{name}__{cmd or 'ask'}__{mode}: {exc}")
|
|
248
293
|
continue
|
|
249
294
|
fname = f"{name}__{cmd or 'ask'}__{mode}.json"
|
|
250
295
|
(out_dir / fname).write_text(json.dumps(cell, indent=2) + "\n", encoding="utf-8")
|
|
@@ -276,13 +321,20 @@ def main() -> int:
|
|
|
276
321
|
print(f" p50={w['p50']}ms p95={w['p95']}ms{_gate_text} -> {fname}")
|
|
277
322
|
|
|
278
323
|
print(f"done. artifacts in {out_dir}")
|
|
324
|
+
if not_measured:
|
|
325
|
+
# Written before the gate section: a cell that was not measured is the
|
|
326
|
+
# failure the gate exists to notice, and it must not be reported as a
|
|
327
|
+
# green run with a smaller population (C3-110).
|
|
328
|
+
print("cells not measured:", file=sys.stderr)
|
|
329
|
+
for line in not_measured:
|
|
330
|
+
print(f" {line}", file=sys.stderr)
|
|
279
331
|
if failures:
|
|
280
332
|
print("perf regression gate failed:", file=sys.stderr)
|
|
281
333
|
for fname, gate in failures:
|
|
282
334
|
detail = gate.get("failed_metrics") or gate.get("reasons")
|
|
283
335
|
print(f" {fname}: {gate.get('status')} {detail}", file=sys.stderr)
|
|
284
336
|
return 1
|
|
285
|
-
return 0
|
|
337
|
+
return 1 if not_measured else 0
|
|
286
338
|
|
|
287
339
|
|
|
288
340
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regenerate `sourcecode.gate_anchors` from the committed gate baseline (C3-109).
|
|
3
|
+
|
|
4
|
+
The field anchors in `cache_model` are the **field's** measurements, taken on a
|
|
5
|
+
private repository and a Windows machine. This repository can never refresh
|
|
6
|
+
them, which is why B17 reopened one release after it closed: the mechanism that
|
|
7
|
+
labels an anchor with its build works, and nothing can re-measure the number it
|
|
8
|
+
labels.
|
|
9
|
+
|
|
10
|
+
What this repository *can* measure every release is the gate — `perf-gate.yml`
|
|
11
|
+
runs a declared command population on a 2 985-file repository under a declared
|
|
12
|
+
host class, and commits the cells. This script copies those cells into the
|
|
13
|
+
package, so a build can publish an in-house figure for **itself** beside a field
|
|
14
|
+
anchor that belongs to an older one.
|
|
15
|
+
|
|
16
|
+
One authority: the baseline JSON. The module is a derived artifact and
|
|
17
|
+
`tests/test_gate_anchor_currency.py` fails when the two disagree, so a refreshed
|
|
18
|
+
baseline that never reached the package is a red suite rather than a silent
|
|
19
|
+
lie — which is the failure mode the whole row is about.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
python3 scripts/sync_gate_anchors.py # rewrite the module
|
|
23
|
+
python3 scripts/sync_gate_anchors.py --check # exit 1 if it is out of date
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
33
|
+
BASELINE_DIR = REPO_ROOT / "docs" / "perf" / "baselines" / "gate-latest"
|
|
34
|
+
MODULE_PATH = REPO_ROOT / "src" / "sourcecode" / "gate_anchors.py"
|
|
35
|
+
|
|
36
|
+
_HEADER = '''"""gate_anchors.py — what the release gate measured, on the build that ran it.
|
|
37
|
+
|
|
38
|
+
**Generated by `scripts/sync_gate_anchors.py` from `docs/perf/baselines/gate-latest/`.
|
|
39
|
+
Do not edit by hand.** The baseline is the authority; this module is how a build
|
|
40
|
+
carries it.
|
|
41
|
+
|
|
42
|
+
C3-109. `cache_model`'s field anchors are the field's own measurements, on their
|
|
43
|
+
repository and their machine — the reason they are trustworthy is the reason they
|
|
44
|
+
cannot be refreshed here, and B17 reopened one release after closing because a
|
|
45
|
+
refreshed anchor expires the moment the next release ships. This is the figure
|
|
46
|
+
this project *can* re-measure every release: the gate's own population on a
|
|
47
|
+
2 985-file repository, under a declared host class, with the build that produced
|
|
48
|
+
it recorded beside it.
|
|
49
|
+
|
|
50
|
+
It does not replace a field anchor and never overrides one. It is the second
|
|
51
|
+
figure a reader gets — one measured at field scale on another build, one measured
|
|
52
|
+
on this build on a repository we control — and the pair is honest in a way
|
|
53
|
+
neither is alone.
|
|
54
|
+
"""
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
'''
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _cells() -> "dict[str, dict]":
|
|
61
|
+
out: "dict[str, dict]" = {}
|
|
62
|
+
for path in sorted(BASELINE_DIR.glob("*.json")):
|
|
63
|
+
cell = json.loads(path.read_text(encoding="utf-8"))
|
|
64
|
+
command = cell.get("command") or "ask"
|
|
65
|
+
out[command] = {
|
|
66
|
+
"seconds": round(float(cell["wall_ms"]["p50"]) / 1000.0, 2),
|
|
67
|
+
"mode": cell.get("mode", "unknown"),
|
|
68
|
+
"runs": int(cell.get("runs", 0) or 0),
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _meta() -> dict:
|
|
74
|
+
first = next(iter(sorted(BASELINE_DIR.glob("*.json"))), None)
|
|
75
|
+
if first is None:
|
|
76
|
+
return {}
|
|
77
|
+
cell = json.loads(first.read_text(encoding="utf-8"))
|
|
78
|
+
return {
|
|
79
|
+
"version": cell.get("tool_version", ""),
|
|
80
|
+
"captured_at": cell.get("captured_at", ""),
|
|
81
|
+
"repository": (cell.get("repo") or {}).get("name", ""),
|
|
82
|
+
"java_files": int((cell.get("repo") or {}).get("files", 0) or 0),
|
|
83
|
+
"host_class": (cell.get("env") or {}).get("host_id", "") or "github-ubuntu-latest",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def render() -> str:
|
|
88
|
+
meta = _meta()
|
|
89
|
+
cells = _cells()
|
|
90
|
+
lines = [_HEADER]
|
|
91
|
+
lines.append("#: The build the gate cells below were captured on.\n")
|
|
92
|
+
lines.append(f'GATE_MEASURED_VERSION = {meta.get("version", "")!r}\n')
|
|
93
|
+
lines.append("#: When, so a reader can tell a stale capture from a missing one.\n")
|
|
94
|
+
lines.append(f'GATE_CAPTURED_AT = {meta.get("captured_at", "")!r}\n')
|
|
95
|
+
lines.append("#: The repository the gate measures, and its size axis.\n")
|
|
96
|
+
lines.append(f'GATE_REPOSITORY = {meta.get("repository", "")!r}\n')
|
|
97
|
+
lines.append(f'GATE_JAVA_FILES = {meta.get("java_files", 0)!r}\n')
|
|
98
|
+
lines.append(
|
|
99
|
+
"#: The host class the runs are comparable within. Cross-machine numbers\n"
|
|
100
|
+
"#: are recorded, never diffed (ADR-0006).\n"
|
|
101
|
+
)
|
|
102
|
+
lines.append(f'GATE_HOST_CLASS = {meta.get("host_class", "")!r}\n')
|
|
103
|
+
lines.append(
|
|
104
|
+
"\n#: command → measured wall time (p50 seconds), the cache mode it was\n"
|
|
105
|
+
"#: taken in, and how many runs the median is over.\n"
|
|
106
|
+
)
|
|
107
|
+
lines.append("GATE_ANCHORS: \"dict[str, dict]\" = {\n")
|
|
108
|
+
for command, cell in cells.items():
|
|
109
|
+
lines.append(
|
|
110
|
+
f" {command!r}: {{\"seconds\": {cell['seconds']!r}, "
|
|
111
|
+
f"\"mode\": {cell['mode']!r}, \"runs\": {cell['runs']!r}}},\n"
|
|
112
|
+
)
|
|
113
|
+
lines.append("}\n")
|
|
114
|
+
return "".join(lines)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> int:
|
|
118
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
119
|
+
ap.add_argument("--check", action="store_true",
|
|
120
|
+
help="Do not write; exit 1 when the module is out of date.")
|
|
121
|
+
args = ap.parse_args()
|
|
122
|
+
|
|
123
|
+
if not BASELINE_DIR.exists():
|
|
124
|
+
print(f"no baseline directory at {BASELINE_DIR}", file=sys.stderr)
|
|
125
|
+
return 1
|
|
126
|
+
rendered = render()
|
|
127
|
+
current = MODULE_PATH.read_text(encoding="utf-8") if MODULE_PATH.exists() else ""
|
|
128
|
+
if args.check:
|
|
129
|
+
if current != rendered:
|
|
130
|
+
print(
|
|
131
|
+
"sourcecode/gate_anchors.py is out of date with "
|
|
132
|
+
"docs/perf/baselines/gate-latest — run scripts/sync_gate_anchors.py",
|
|
133
|
+
file=sys.stderr,
|
|
134
|
+
)
|
|
135
|
+
return 1
|
|
136
|
+
print("gate anchors are in sync")
|
|
137
|
+
return 0
|
|
138
|
+
MODULE_PATH.write_text(rendered, encoding="utf-8")
|
|
139
|
+
print(f"wrote {MODULE_PATH.relative_to(REPO_ROOT)}")
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
raise SystemExit(main())
|