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