sourcecode 4.17.0__tar.gz → 4.18.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-4.17.0 → sourcecode-4.18.0}/CHANGELOG.md +44 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/PKG-INFO +3 -3
- {sourcecode-4.17.0 → sourcecode-4.18.0}/README.md +2 -2
- {sourcecode-4.17.0 → sourcecode-4.18.0}/pyproject.toml +1 -1
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/cli.py +18 -13
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/context_cache.py +94 -7
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/identity_fallback.py +6 -12
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/posture.py +9 -14
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/risk.py +29 -6
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/security_config_scan.py +4 -8
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/source_text.py +65 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/.github/workflows/perf-gate.yml +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/.gitignore +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/.ruff.toml +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/CLAUDE.md +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/CONTRIBUTING.md +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/LICENSE +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/SECURITY.md +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/raw +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/scripts/perf_gate.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/scripts/perf_harness.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/container_wiring.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/declarations.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detach.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/execution_plan.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/license.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/dependency_import.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/output_encoding.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/parallel.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/partial_contract.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/phased_run.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/provenance.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/readonly.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/release_info.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/rule_pass.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/runs.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/text_input.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/functions/README.md +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.17.0 → sourcecode-4.18.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.18.0] — 2026-08-10
|
|
6
|
+
|
|
7
|
+
**A field battery over the test repositories, run against 4.17.0 and aimed first
|
|
8
|
+
at where the time goes.** Three defects, all found by measuring rather than by
|
|
9
|
+
reading: one answer that was silently about the wrong repository, and two scans
|
|
10
|
+
that could not finish. `risk` on BroadleafCommerce (2 985 files) did not return in
|
|
11
|
+
twenty minutes and now returns a complete answer in **68 s**.
|
|
12
|
+
|
|
13
|
+
**Fixed in this release:**
|
|
14
|
+
|
|
15
|
+
- **A run bounded to a subdirectory answered for the whole repository (C3-94).**
|
|
16
|
+
The shared Java parse was keyed on the worktree alone, so a run over a
|
|
17
|
+
subdirectory stored its subset under the repository-wide key and the next
|
|
18
|
+
repository-wide command read it. Measured on openmrs-core: `explain WebUtil
|
|
19
|
+
<repo>/web` left a **42-file / 382-symbol** CIR where the repository has 861 and
|
|
20
|
+
11 605, and `impact-chain` then answered from it — reporting 861 files beside
|
|
21
|
+
382 symbols, and dropping from `confidence: high` to `low` with no input change.
|
|
22
|
+
The analysed set is now part of the key; a repository-wide run owns the shared
|
|
23
|
+
entry and a bounded run keys on its own, so bounded runs are **cached** instead
|
|
24
|
+
of being exempt. The rule lived in one call site and four others wrote the key
|
|
25
|
+
unguarded; it now lives in the cache, behind one door.
|
|
26
|
+
- **Naming the enclosing type backtracked exponentially (C3-95).** Two scanners
|
|
27
|
+
carried the same ambiguous pattern — a whitespace branch inside a starred group,
|
|
28
|
+
followed by another starred whitespace group. Measured: **13 ms** for 200 leading
|
|
29
|
+
spaces on one line, 1,17 s for 800, **10,1 s for 1 600**. Now linear: 0,06 ms at
|
|
30
|
+
1 600 and 0,76 ms at 20 000. `scan_security_configuration` on BroadleafCommerce
|
|
31
|
+
**3,34 s → 1,53 s**, same findings.
|
|
32
|
+
- **`risk` could not finish, inside a regex no budget can interrupt (C3-96).**
|
|
33
|
+
`_java_methods` runs a method pattern over every source, and that pattern let the
|
|
34
|
+
same whitespace be consumed in three places at once. A catastrophic match runs in
|
|
35
|
+
CPython's C regex engine, which never returns to the interpreter — so the
|
|
36
|
+
deadline C3-87 installed is not read, and neither is a Ctrl-C. Rewritten so every
|
|
37
|
+
separator is consumed once, with the matches asserted **identical** over 1 771
|
|
38
|
+
files: eureka **142,35 s → 2,10 s**, jobrunr 29,38 s → 1,75 s, openmrs-core
|
|
39
|
+
35,25 s → 11,11 s, and `risk` on BroadleafCommerce **>20 min (killed) → 68 s**,
|
|
40
|
+
15 of 15 defects composed.
|
|
41
|
+
|
|
42
|
+
**Not claimed by this release:** routing `spring-audit` and `risk` onto the shared
|
|
43
|
+
parse is a correctness change, not a measured speed-up — the per-file parse cache
|
|
44
|
+
already absorbed most of it, and the A/B on openmrs-core and BroadleafCommerce
|
|
45
|
+
came out inside the noise. The residual cost of the method scan is polynomial, not
|
|
46
|
+
linear, and `migrate-check` and `endpoints` still gain nothing from a warm; both
|
|
47
|
+
are open in `.planning/ROADMAP-FIELD-BATTERY-2026-08-10.md`.
|
|
48
|
+
|
|
5
49
|
## [4.17.0] — 2026-08-10
|
|
6
50
|
|
|
7
51
|
**The Part 1 queues of field evaluations #20 and #21, and the first three
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.18.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 4.
|
|
129
|
+
ask version # ask 4.18.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 4.
|
|
91
|
+
ask version # ask 4.18.0 — and, on a build that has aged,
|
|
92
92
|
# how many releases have probably shipped since
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -8285,14 +8285,20 @@ def spring_audit_cmd(
|
|
|
8285
8285
|
)
|
|
8286
8286
|
_stopped_early: Optional[str] = None
|
|
8287
8287
|
|
|
8288
|
+
from sourcecode import context_cache as _ctxcache
|
|
8289
|
+
|
|
8288
8290
|
phase = f"auditing {len(file_list)} Java files"
|
|
8289
8291
|
with _expensive_analysis_scope("spring-audit", target, phase):
|
|
8290
8292
|
_prog = Progress()
|
|
8291
8293
|
_prog.start(phase)
|
|
8292
8294
|
try:
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8295
|
+
# The parse is ~89 % of this command's wall clock (measured: 8,54 s of
|
|
8296
|
+
# 9,53 s on a 1 303-file repository) and it is the same parse the other
|
|
8297
|
+
# knowledge commands share, so it is fetched rather than rebuilt — which
|
|
8298
|
+
# is what makes a warm worth anything here (cold == warm before C3-94).
|
|
8299
|
+
cir = _ctxcache.shared_cir(
|
|
8300
|
+
target, file_list, progress=_work_sink(_prog)
|
|
8301
|
+
)
|
|
8296
8302
|
# C3-85: the stretch between the last `linking` tick and the first rule
|
|
8297
8303
|
# tick reports nothing, and what stays on the line is `n/n` — a counter
|
|
8298
8304
|
# that finished, reading as a stage still running. Measured on keycloak
|
|
@@ -9966,7 +9972,7 @@ def _migration_blast_radius(
|
|
|
9966
9972
|
from sourcecode.spring_model import SpringSemanticModel
|
|
9967
9973
|
|
|
9968
9974
|
try:
|
|
9969
|
-
cir
|
|
9975
|
+
cir = _ctxcache.shared_cir(target, file_list)
|
|
9970
9976
|
except Exception:
|
|
9971
9977
|
cir = ContextGraph.build(file_list, target).cir
|
|
9972
9978
|
model = SpringSemanticModel.build(cir)
|
|
@@ -10520,10 +10526,7 @@ def impact_chain_cmd(
|
|
|
10520
10526
|
# a fresh build, exactly as explain does, so impact-chain never breaks.
|
|
10521
10527
|
from sourcecode import context_cache as _ctxcache
|
|
10522
10528
|
try:
|
|
10523
|
-
cir
|
|
10524
|
-
_resolve_repo_root(target), target, file_list,
|
|
10525
|
-
progress=_work_sink(_prog),
|
|
10526
|
-
)
|
|
10529
|
+
cir = _ctxcache.shared_cir(target, file_list, progress=_work_sink(_prog))
|
|
10527
10530
|
except Exception:
|
|
10528
10531
|
cir = ContextGraph.build(file_list, target, progress=_work_sink(_prog)).cir
|
|
10529
10532
|
_model = SpringSemanticModel.build(cir)
|
|
@@ -10849,11 +10852,13 @@ def explain_cmd(
|
|
|
10849
10852
|
)
|
|
10850
10853
|
raise typer.Exit(code=1)
|
|
10851
10854
|
|
|
10852
|
-
# AI Context Cache — operates at the *knowledge* level: it caches the
|
|
10853
|
-
#
|
|
10854
|
-
#
|
|
10855
|
-
# shared CIR
|
|
10856
|
-
# Provider-agnostic and best-effort: any fault degrades to a fresh build.
|
|
10855
|
+
# AI Context Cache — operates at the *knowledge* level: it caches the reusable
|
|
10856
|
+
# Canonical IR (the expensive Java parse), keyed by knowledge state and by the
|
|
10857
|
+
# analysed set, not by command. explain then derives its answer cheaply from
|
|
10858
|
+
# that shared CIR, and any other command over the same files reuses it for
|
|
10859
|
+
# free. Provider-agnostic and best-effort: any fault degrades to a fresh build.
|
|
10860
|
+
# This call site keeps the lower-level entry point because it reports the
|
|
10861
|
+
# lookup (`_cc_look`); `shared_cir` is the same door without the receipt.
|
|
10857
10862
|
_prog = Progress()
|
|
10858
10863
|
_prog.start(f"explaining {class_name} ({len(file_list)} files)")
|
|
10859
10864
|
_cc_look = None
|
|
@@ -651,6 +651,33 @@ class KnowledgeLookup:
|
|
|
651
651
|
return f"Context cache: MISS (build {self.build_ms:.0f}ms)"
|
|
652
652
|
|
|
653
653
|
|
|
654
|
+
def analysed_set_signature(root: Path, file_list: list[str]) -> str:
|
|
655
|
+
"""Fingerprint of *what was analysed*: the scope root and the exact file set.
|
|
656
|
+
|
|
657
|
+
The shared CIR is keyed on knowledge state, and until C3-94 "knowledge state"
|
|
658
|
+
was read as the worktree alone — which is only half of it. A run bounded to a
|
|
659
|
+
subdirectory parses a *subset* of the same worktree, and the resulting CIR is
|
|
660
|
+
a different artifact answering a different question. Keyed on the worktree
|
|
661
|
+
alone, both artifacts collided on one entry: measured on a 861-file repository,
|
|
662
|
+
``explain WebUtil <repo>/web`` stored a 42-file / 382-symbol CIR under the
|
|
663
|
+
repo-wide key, and the next repo-wide command read it and answered about 3 % of
|
|
664
|
+
the repository — silently, because the reader passes its own ``file_paths`` to
|
|
665
|
+
``ir_dict_to_canonical`` and the reconstructed CIR then reports 861 files
|
|
666
|
+
beside 382 symbols.
|
|
667
|
+
|
|
668
|
+
So the analysed set is stated, not hoped for. The scope path and the file list
|
|
669
|
+
are both in it: two roots can yield the same file count, and a caller that
|
|
670
|
+
filters its list (tests in or out) is asking about a different population than
|
|
671
|
+
one that does not.
|
|
672
|
+
"""
|
|
673
|
+
payload = "".join([
|
|
674
|
+
str(root.resolve()),
|
|
675
|
+
str(len(file_list)),
|
|
676
|
+
hashlib.sha256("\n".join(sorted(file_list)).encode("utf-8")).hexdigest(),
|
|
677
|
+
])
|
|
678
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]
|
|
679
|
+
|
|
680
|
+
|
|
654
681
|
def get_or_build_cir(
|
|
655
682
|
repo_root: Path,
|
|
656
683
|
root: Path,
|
|
@@ -662,11 +689,19 @@ def get_or_build_cir(
|
|
|
662
689
|
"""Return ``(CanonicalRepositoryIR, KnowledgeLookup)``, caching the CIR.
|
|
663
690
|
|
|
664
691
|
This is the knowledge-level entry point. It caches the **reusable** raw IR
|
|
665
|
-
(the expensive Java parse) keyed
|
|
666
|
-
so every command that
|
|
667
|
-
CIR is rebuilt cheaply from the cached raw IR via
|
|
668
|
-
skipping the parse entirely. Best-effort: any cache
|
|
669
|
-
fresh build.
|
|
692
|
+
(the expensive Java parse) keyed by knowledge state and by the analysed set —
|
|
693
|
+
not by command — so every command that parses the same files shares one cache
|
|
694
|
+
entry. On a hit the full CIR is rebuilt cheaply from the cached raw IR via
|
|
695
|
+
``ir_dict_to_canonical``, skipping the parse entirely. Best-effort: any cache
|
|
696
|
+
fault falls back to a fresh build.
|
|
697
|
+
|
|
698
|
+
A **repo-wide** run (``root`` is ``repo_root``) owns the scope-free entry —
|
|
699
|
+
the one :func:`peek_cir` reads without knowing any file list, which is why
|
|
700
|
+
that key must stay free of one. Every **bounded** run keys on its own analysed
|
|
701
|
+
set instead, so it can neither read nor overwrite the repo-wide answer. Either
|
|
702
|
+
way the entry carries the analysed signature it was built from and a reader
|
|
703
|
+
whose set does not match rebuilds: the key prevents the collision, the check
|
|
704
|
+
makes the prevention verifiable rather than assumed (C3-94).
|
|
670
705
|
|
|
671
706
|
`progress` is the (done, total, stage) sink the caller's heartbeat reads
|
|
672
707
|
(C3-56). It reaches only the *building* paths, which is the honest place for
|
|
@@ -684,14 +719,23 @@ def get_or_build_cir(
|
|
|
684
719
|
cir = build_canonical_ir(file_list, root, since=since, progress=progress)
|
|
685
720
|
return cir, KnowledgeLookup(hit=False, enabled=False)
|
|
686
721
|
|
|
687
|
-
|
|
722
|
+
analysed = analysed_set_signature(root, file_list)
|
|
723
|
+
options: dict[str, Any] = {"since": since} if since else None
|
|
724
|
+
if root.resolve() != repo_root.resolve():
|
|
725
|
+
# A bounded run: its own entry, never the repo-wide one.
|
|
726
|
+
options = dict(options or {})
|
|
727
|
+
options["analysed"] = analysed
|
|
728
|
+
key = cache.knowledge_key(SCOPE_JAVA_CIR, options=options)
|
|
688
729
|
|
|
689
730
|
t0 = time.perf_counter()
|
|
690
731
|
cached = cache.get(key)
|
|
691
732
|
lookup_ms = (time.perf_counter() - t0) * 1000
|
|
692
733
|
if cached is not None:
|
|
693
734
|
raw = cached.payload.get("raw_ir")
|
|
694
|
-
|
|
735
|
+
stored = (cached.metadata or {}).get("analysed_set")
|
|
736
|
+
# An entry written before C3-94 carries no signature; it cannot be shown to
|
|
737
|
+
# describe this analysed set, so it is not served as if it did.
|
|
738
|
+
if isinstance(raw, dict) and stored == analysed:
|
|
695
739
|
try:
|
|
696
740
|
cir = ir_dict_to_canonical(raw, file_paths=file_list)
|
|
697
741
|
if not validate_canonical_ir(cir):
|
|
@@ -708,6 +752,8 @@ def get_or_build_cir(
|
|
|
708
752
|
"scope": SCOPE_JAVA_CIR,
|
|
709
753
|
"cir_hash": cir.cir_hash,
|
|
710
754
|
"schema_version": cir.schema_version,
|
|
755
|
+
"analysed_set": analysed,
|
|
756
|
+
"analysed_root": str(root.resolve()),
|
|
711
757
|
"file_count": len(cir.files),
|
|
712
758
|
"symbol_count": len(cir.symbols),
|
|
713
759
|
},
|
|
@@ -725,6 +771,41 @@ def get_or_build_cir(
|
|
|
725
771
|
return cir, KnowledgeLookup(hit=False, enabled=True, build_ms=build_ms)
|
|
726
772
|
|
|
727
773
|
|
|
774
|
+
def repo_root_of(root: Path) -> Path:
|
|
775
|
+
"""The enclosing git root, or *root* itself outside a repository."""
|
|
776
|
+
candidate = root.resolve()
|
|
777
|
+
while True:
|
|
778
|
+
if (candidate / ".git").exists():
|
|
779
|
+
return candidate
|
|
780
|
+
if candidate.parent == candidate:
|
|
781
|
+
return root.resolve()
|
|
782
|
+
candidate = candidate.parent
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def shared_cir(
|
|
786
|
+
root: Path,
|
|
787
|
+
file_list: list[str],
|
|
788
|
+
*,
|
|
789
|
+
since: Optional[str] = None,
|
|
790
|
+
progress: "Optional[Callable[[int, int, str], None]]" = None,
|
|
791
|
+
) -> Any:
|
|
792
|
+
"""The Java CIR for *root*, from the shared knowledge cache.
|
|
793
|
+
|
|
794
|
+
The one door for a command that needs the repository parse and does not care
|
|
795
|
+
where it comes from: it resolves the enclosing repository itself, so a caller
|
|
796
|
+
holds no opinion about cache scope — the reason each call-site that built its
|
|
797
|
+
own CIR was one more place for the scope rule to be got wrong (C3-94).
|
|
798
|
+
|
|
799
|
+
Best-effort by construction: `get_or_build_cir` degrades to a fresh build on
|
|
800
|
+
any cache fault, so the answer never depends on the cache being there.
|
|
801
|
+
"""
|
|
802
|
+
cir, _lookup = get_or_build_cir(
|
|
803
|
+
repo_root_of(root), Path(root).resolve(), file_list,
|
|
804
|
+
since=since, progress=progress,
|
|
805
|
+
)
|
|
806
|
+
return cir
|
|
807
|
+
|
|
808
|
+
|
|
728
809
|
def peek_cir(
|
|
729
810
|
repo_root: Path,
|
|
730
811
|
*,
|
|
@@ -755,6 +836,12 @@ def peek_cir(
|
|
|
755
836
|
raw = cached.payload.get("raw_ir")
|
|
756
837
|
if not isinstance(raw, dict):
|
|
757
838
|
return None
|
|
839
|
+
# The caller is promised the *repo-wide* CIR, so the entry has to say it is one.
|
|
840
|
+
# C3-94 makes the key unreachable to bounded runs; this re-reads the fact from
|
|
841
|
+
# the entry rather than inferring it from the key, and treats an entry written
|
|
842
|
+
# before that guarantee (no recorded root) as a miss.
|
|
843
|
+
if (cached.metadata or {}).get("analysed_root") != str(repo_root.resolve()):
|
|
844
|
+
return None
|
|
758
845
|
try:
|
|
759
846
|
cir = ir_dict_to_canonical(raw, file_paths=None)
|
|
760
847
|
# validate_canonical_ir returns a LIST of problems — empty means valid.
|
|
@@ -81,11 +81,6 @@ _METHOD_RE = re.compile(
|
|
|
81
81
|
re.MULTILINE,
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
-
_TYPE_RE = re.compile(
|
|
85
|
-
r"^[ \t]*(?:public|final|abstract|static|\s)*\s*(?:class|interface|enum)\s+(?P<name>\w+)",
|
|
86
|
-
re.MULTILINE,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
84
|
#: Returns that are not an identity even inside an absence branch: re-dispatch,
|
|
90
85
|
#: constants and the empty-optional answers, which are refusals in disguise.
|
|
91
86
|
_NON_IDENTITY_RETURNS = re.compile(
|
|
@@ -126,13 +121,12 @@ class IdentityFallback:
|
|
|
126
121
|
|
|
127
122
|
def _enclosing_type(source: str, offset: int) -> str:
|
|
128
123
|
"""The type declared closest above `offset`. Nested types resolve to the nearest
|
|
129
|
-
enclosing declaration, which is the one a reader would name.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return name
|
|
124
|
+
enclosing declaration, which is the one a reader would name.
|
|
125
|
+
|
|
126
|
+
Thin alias of the one authority (C3-95) — see `source_text.enclosing_type`."""
|
|
127
|
+
from sourcecode.source_text import enclosing_type
|
|
128
|
+
|
|
129
|
+
return enclosing_type(source, offset, "")
|
|
136
130
|
|
|
137
131
|
|
|
138
132
|
def _method_body(source: str, brace_index: int) -> str:
|
|
@@ -1109,27 +1109,22 @@ def _repo_root_of(root: Path) -> Path:
|
|
|
1109
1109
|
|
|
1110
1110
|
|
|
1111
1111
|
def shared_cir(root: Path, files: "list[str]"):
|
|
1112
|
-
"""The Java CIR for *root*, from the shared knowledge cache
|
|
1112
|
+
"""The Java CIR for *root*, from the shared knowledge cache.
|
|
1113
1113
|
|
|
1114
1114
|
`posture` built its own CIR on every run — the same parse `explain`,
|
|
1115
1115
|
`impact-chain` and `cache warm` already share — so the one command the
|
|
1116
1116
|
product differentiates on was the one a warm could not help (C3-25).
|
|
1117
1117
|
|
|
1118
|
-
Poison-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1118
|
+
Poison-safety used to live here: the shared key was repo-wide and carried no
|
|
1119
|
+
scope, so a run bounded to a subdirectory had to keep its own unshared build
|
|
1120
|
+
or it would store a truncated CIR under the repo-wide key. C3-94 moved that
|
|
1121
|
+
rule into the cache itself — a bounded run now keys on its own analysed set —
|
|
1122
|
+
so the bounded case is cached too instead of paying for the parse every time,
|
|
1123
|
+
and this function is the thin alias of the one authority it left behind.
|
|
1123
1124
|
"""
|
|
1124
|
-
from sourcecode.
|
|
1125
|
+
from sourcecode.context_cache import shared_cir as _shared_cir
|
|
1125
1126
|
|
|
1126
|
-
|
|
1127
|
-
if repo_root != root:
|
|
1128
|
-
return ContextGraph.build(files, root).cir
|
|
1129
|
-
from sourcecode.context_cache import get_or_build_cir
|
|
1130
|
-
|
|
1131
|
-
cir, _lookup = get_or_build_cir(repo_root, root, files)
|
|
1132
|
-
return cir
|
|
1127
|
+
return _shared_cir(root, files)
|
|
1133
1128
|
|
|
1134
1129
|
|
|
1135
1130
|
def _posture(
|
|
@@ -150,10 +150,30 @@ _URL_PATTERN_RE = re.compile(
|
|
|
150
150
|
re.IGNORECASE,
|
|
151
151
|
)
|
|
152
152
|
_HTTP_INPUT_ANNOTATION_RE = re.compile(r"@(RequestParam|PathVariable|RequestBody)\b")
|
|
153
|
+
#: C3-96. Three of this pattern's groups used to let the same whitespace be
|
|
154
|
+
#: consumed in more than one place — `(?:\s*@…\s*)*`, `\s*(?:public|…)?\s*`, and
|
|
155
|
+
#: `[\w<>\[\].?,\s]+\s+`, whose character class contains the `\s` the following
|
|
156
|
+
#: `\s+` also matches. Each run of whitespace can then be split between the two in
|
|
157
|
+
#: many ways, and on text that never completes a signature the engine tries them
|
|
158
|
+
#: all: `_java_methods` runs this over **every** source, and a repository-wide
|
|
159
|
+
#: `risk` on a 2 985-file repository did not finish in 20 minutes — inside the C
|
|
160
|
+
#: regex engine, where no signal, budget or Ctrl-C can reach it (C3-87's deadline
|
|
161
|
+
#: is checked between stages, and this never returns to one).
|
|
162
|
+
#:
|
|
163
|
+
#: Rewritten so every separator is consumed once: each modifier takes its own
|
|
164
|
+
#: trailing whitespace, and the return type is tokens joined by explicit
|
|
165
|
+
#: separators instead of one class that contains the separator too.
|
|
166
|
+
#:
|
|
167
|
+
#: The **same** language, deliberately, including the part that reads like a bug:
|
|
168
|
+
#: the old class matched whitespace alone, so a bare `if (cond) {` was admitted as
|
|
169
|
+
#: a method named `if`. Those pseudo-methods carry propagation through control-flow
|
|
170
|
+
#: blocks in the walk below, so dropping them is a change to what `risk` finds, not
|
|
171
|
+
#: a speed-up — which is why the type stays optional here and the question of
|
|
172
|
+
#: whether it should is left to its own increment, with its own field measurement.
|
|
153
173
|
_JAVA_METHOD_RE = re.compile(
|
|
154
|
-
r"(?P<ann>(
|
|
155
|
-
r"(?P<sig
|
|
156
|
-
r"[\w<>\[\]
|
|
174
|
+
r"(?P<ann>(?:@[\w.]+(?:\([^;{}]*?\))?\s*)*)"
|
|
175
|
+
r"(?P<sig>(?:(?:public|protected|private)\s+)?(?:static\s+)?"
|
|
176
|
+
r"(?:[\w<>\[\].?,]+(?:\s+[\w<>\[\].?,]+)*\s+|\s{2,})(?P<name>[A-Za-z_$][\w$]*)\s*"
|
|
157
177
|
r"\((?P<params>[^)]*)\)\s*)\{",
|
|
158
178
|
re.MULTILINE,
|
|
159
179
|
)
|
|
@@ -797,7 +817,7 @@ class RiskComposer:
|
|
|
797
817
|
profiles: "Optional[set[str]]" = None,
|
|
798
818
|
progress: "Optional[Callable[..., Any]]" = None,
|
|
799
819
|
) -> None:
|
|
800
|
-
from sourcecode.
|
|
820
|
+
from sourcecode.context_cache import shared_cir
|
|
801
821
|
from sourcecode.repository_ir import extract_java_endpoints, find_java_files
|
|
802
822
|
from sourcecode.security_posture import (
|
|
803
823
|
endpoint_security_surface,
|
|
@@ -808,8 +828,11 @@ class RiskComposer:
|
|
|
808
828
|
from sourcecode.spring_tx_analyzer import run_tx_audit
|
|
809
829
|
|
|
810
830
|
self.root = Path(root).resolve()
|
|
811
|
-
|
|
812
|
-
|
|
831
|
+
# The parse is ~80 % of this command's wall clock (measured: 12,45 s of
|
|
832
|
+
# 15,64 s on a 1 303-file repository) and it is the same parse `posture`,
|
|
833
|
+
# `explain` and `cache warm` already share — so it is fetched, not rebuilt.
|
|
834
|
+
self.cir = cir if cir is not None else shared_cir(
|
|
835
|
+
self.root, find_java_files(self.root)
|
|
813
836
|
)
|
|
814
837
|
self.model = SpringSemanticModel.build(self.cir)
|
|
815
838
|
|
|
@@ -249,14 +249,10 @@ def _line_of(source: str, index: int) -> int:
|
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
def _enclosing_type(source: str, offset: int, fallback: str) -> str:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
re.MULTILINE,
|
|
257
|
-
):
|
|
258
|
-
name = m.group("n")
|
|
259
|
-
return name
|
|
252
|
+
"""Thin alias of the one authority (C3-95) — see `source_text.enclosing_type`."""
|
|
253
|
+
from sourcecode.source_text import enclosing_type
|
|
254
|
+
|
|
255
|
+
return enclosing_type(source, offset, fallback)
|
|
260
256
|
|
|
261
257
|
|
|
262
258
|
def _scan_java(source: str, rel: str) -> "list[SecurityConfigObservation]":
|
|
@@ -30,6 +30,7 @@ other.
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import re
|
|
33
|
+
from typing import Optional
|
|
33
34
|
|
|
34
35
|
#: Java/C-family lexical tokens, in precedence order. String and character
|
|
35
36
|
#: literals are matched **first** so that a `//` or `/*` living inside one — as in
|
|
@@ -84,6 +85,70 @@ def blank_hash_comments(source: str) -> str:
|
|
|
84
85
|
return _HASH_COMMENT_LINE.sub(lambda m: _blank(m.group(0)), source)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
#: The one authority for "a type is declared here", used by every scanner that has
|
|
89
|
+
#: to name the type enclosing a match it found in raw text.
|
|
90
|
+
#:
|
|
91
|
+
#: C3-95. Two modules kept their own copy, and both were written as
|
|
92
|
+
#: ``(?:public|final|abstract|static|\s)*\s*`` — a whitespace branch inside a
|
|
93
|
+
#: starred group followed by another starred whitespace group. Every run of spaces
|
|
94
|
+
#: can then be split between the two in exponentially many ways, and on a line that
|
|
95
|
+
#: never reaches `class` the engine tries them all before failing: measured at
|
|
96
|
+
#: 13 ms for 200 leading spaces, 1,17 s for 800 and **10,1 s for 1 600** — one line,
|
|
97
|
+
#: one file. A generated or reformatted source is enough to hang a scan that is
|
|
98
|
+
#: supposed to take seconds, and `spring-audit` and `risk` both run this scan.
|
|
99
|
+
#:
|
|
100
|
+
#: Written so each modifier must consume its own trailing whitespace, the group is
|
|
101
|
+
#: unambiguous and the match is linear. It accepts the same declarations: the
|
|
102
|
+
#: modifier list is wider than the original (which silently rejected
|
|
103
|
+
#: `private`/`protected`/`sealed`), and the separator is still any whitespace, so a
|
|
104
|
+
#: modifier on its own line still binds to the declaration below it.
|
|
105
|
+
JAVA_TYPE_DECLARATION = re.compile(
|
|
106
|
+
r"^[ \t]*"
|
|
107
|
+
r"(?:(?:public|protected|private|final|abstract|static|sealed|non-sealed)\s+)*"
|
|
108
|
+
r"(?:class|interface|enum|record)\s+(?P<name>\w+)",
|
|
109
|
+
re.MULTILINE,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def type_declarations(source: str) -> "list[tuple[int, str]]":
|
|
114
|
+
"""``(offset, name)`` of every type declared in *source*, in source order."""
|
|
115
|
+
return [(m.start(), m.group("name")) for m in JAVA_TYPE_DECLARATION.finditer(source)]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def enclosing_type(source: str, offset: int, fallback: str = "") -> str:
|
|
119
|
+
"""Name of the type declared closest above *offset*, else *fallback*.
|
|
120
|
+
|
|
121
|
+
Nested types resolve to the nearest enclosing declaration, which is the one a
|
|
122
|
+
reader would name. Callers ask this once per observation and a file can carry
|
|
123
|
+
many, so the scan of a given source is memoised: the previous shape sliced
|
|
124
|
+
``source[:offset]`` and re-scanned it per call, which is a copy and a walk of
|
|
125
|
+
the whole prefix for every finding in the file.
|
|
126
|
+
|
|
127
|
+
At the boundary the two prior copies disagreed, and this one keeps
|
|
128
|
+
``identity_fallback``'s rule: the nearest declaration **at or above** the
|
|
129
|
+
offset. Slicing the prefix dropped a declaration that straddled the offset, so
|
|
130
|
+
a match on the declaration's own line was attributed to the type above it or to
|
|
131
|
+
the fallback. Every real observation sits strictly after its declaration, which
|
|
132
|
+
is why the field results are unchanged either way.
|
|
133
|
+
"""
|
|
134
|
+
global _DECL_MEMO
|
|
135
|
+
memo_key = (len(source), hash(source))
|
|
136
|
+
if _DECL_MEMO is None or _DECL_MEMO[0] != memo_key:
|
|
137
|
+
_DECL_MEMO = (memo_key, type_declarations(source))
|
|
138
|
+
name = fallback
|
|
139
|
+
for start, declared in _DECL_MEMO[1]:
|
|
140
|
+
if start > offset:
|
|
141
|
+
break
|
|
142
|
+
name = declared
|
|
143
|
+
return name
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
#: One entry: callers sweep a file's findings together, so the next question is
|
|
147
|
+
#: almost always about the same source. Holding one keeps the memo from becoming a
|
|
148
|
+
#: second copy of every file the process ever read.
|
|
149
|
+
_DECL_MEMO: "Optional[tuple[tuple[int, int], list[tuple[int, str]]]]" = None
|
|
150
|
+
|
|
151
|
+
|
|
87
152
|
def commented_spans(source: str, *, xml: bool) -> "list[tuple[int, int]]":
|
|
88
153
|
"""`(start, end)` offsets of every comment — what was switched off, not what runs.
|
|
89
154
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|