sourcecode 4.10.2__tar.gz → 4.10.4__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.10.2 → sourcecode-4.10.4}/CHANGELOG.md +81 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/PKG-INFO +6 -6
- {sourcecode-4.10.2 → sourcecode-4.10.4}/README.md +2 -2
- {sourcecode-4.10.2 → sourcecode-4.10.4}/pyproject.toml +4 -4
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/architectural_baseline.py +34 -5
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/cache_model.py +41 -1
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/cli.py +71 -9
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/provenance.py +18 -10
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/release_info.py +1 -1
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/security_config_scan.py +26 -10
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_security_audit.py +12 -1
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/verify_edit.py +31 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/.gitignore +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/.ruff.toml +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/CLAUDE.md +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/CONTRIBUTING.md +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/LICENSE +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/SECURITY.md +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/raw +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/scripts/perf_harness.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/license.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/runs.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/source_text.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/functions/README.md +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.10.2 → sourcecode-4.10.4}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.10.4] — 2026-08-07
|
|
6
|
+
|
|
7
|
+
First release of the queue opened by field evaluation #13. Four of its rows close,
|
|
8
|
+
one closes largely, and the suite goes green: `master` was carrying four failing
|
|
9
|
+
tests before this release, none of them noticed by the release that shipped over
|
|
10
|
+
them.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`baseline` no longer emits a JSON object keyed by symbol** (C3-62). PowerShell's
|
|
15
|
+
`ConvertFrom-Json` folds object keys case-insensitively, so two Java symbols
|
|
16
|
+
differing only by case — legal, Java is case-sensitive — collided and aborted
|
|
17
|
+
parsing of the *entire* baseline document on the default Windows consumer. `fan_in`
|
|
18
|
+
now serialises as a sorted array of `{symbol, fan_in}`, the shape `hotspots` already
|
|
19
|
+
used. Baselines captured before this release keep diffing and trending: the reader
|
|
20
|
+
accepts both shapes.
|
|
21
|
+
- **`spring-audit` no longer prints `done` before the answer exists** (C3-54, part 1).
|
|
22
|
+
The completion line was emitted inside the `finally` around the analysis, so a
|
|
23
|
+
teardown landing between it and the write left 6.8 minutes of CPU and no output
|
|
24
|
+
file behind a reported success. It now prints after the write it describes. The
|
|
25
|
+
same reorder also stops `done` from being claimed on a run whose analysis *raised*.
|
|
26
|
+
- **`ask cache model` answers in ~830 tokens instead of ~2 590** (C3-63). One line per
|
|
27
|
+
layer and per command by default; the per-command measured timings and notes move
|
|
28
|
+
behind `--full`. Every command and every layer is still named, and the invalidation
|
|
29
|
+
rule is still stated — the density fix is not a coverage cut. `--json` and
|
|
30
|
+
`--markdown` are unchanged.
|
|
31
|
+
- **The evidence manifest publishes how many claims exist, not how many fitted**
|
|
32
|
+
(invariant I-2). The claim builder returned the moment it reached its 200-claim cap,
|
|
33
|
+
so the real total was never computed and the cap was published in its place, with no
|
|
34
|
+
field carrying the truth. `claims_cap` now reads `{total, total_indexed, capped}`
|
|
35
|
+
with `total` measured before truncation. `capped` is also corrected: exactly 200
|
|
36
|
+
claims reported a cut that had not happened.
|
|
37
|
+
|
|
38
|
+
### Performance
|
|
39
|
+
|
|
40
|
+
- **The `spring-audit` rule pass is 7.8× faster on a large repository** (C3-61):
|
|
41
|
+
42.1 s → 5.4 s measured on keycloak (5 486 non-test sources, 59 144 symbols), with
|
|
42
|
+
the finding set byte-identical before and after. The cost was not spread across the
|
|
43
|
+
file population — one rule was 94 % of it, and inside it `_proxy_bypass` scanned
|
|
44
|
+
every source for every inferred gate token before establishing that the source never
|
|
45
|
+
mentions the token: 98.9 % of the work was provably empty. The guard is an
|
|
46
|
+
equivalence, not a heuristic, and no threads or processes were added, so output
|
|
47
|
+
determinism is untouched. The rule pass is no longer the dominant phase.
|
|
48
|
+
|
|
49
|
+
### Tests
|
|
50
|
+
|
|
51
|
+
- The four batteries that were red on `master` before this release are green:
|
|
52
|
+
`test_flag_normalization` compared a wall-clock timestamp between two runs;
|
|
53
|
+
`test_path_admission` had no invocation row for `audit-report`; and
|
|
54
|
+
`test_invariant_counts_from_measurement` failed on `provenance.py` (a real defect,
|
|
55
|
+
fixed above) and on `audit_report.py` (a false positive from a hand-maintained key
|
|
56
|
+
catalogue, now a rule on the key's final token — the audit's anti-vacuous guard is
|
|
57
|
+
extended rather than trusted).
|
|
58
|
+
- Every fix in this release ships a regression battery that was mutation-checked:
|
|
59
|
+
reverting the fix fails the new tests. Two of them were rewritten after the first
|
|
60
|
+
draft passed under that mutation — a summary that exists but is never reached by the
|
|
61
|
+
command, and an error path that exits before the spinner is created.
|
|
62
|
+
|
|
63
|
+
## [4.10.3] — 2026-08-05
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- **`GATE-001` no longer reads Java control keywords as method calls.** The regex
|
|
68
|
+
body scan behind the gated-endpoint audit treated `if(...)`, `for(...)`,
|
|
69
|
+
`while(...)`, `switch(...)`, `catch(...)` and `synchronized(...)` as same-class
|
|
70
|
+
invocations, so a handler could be reported as `Controller#if`. Those symbols
|
|
71
|
+
reached the `risk` critical band and `audit-report` top risks. Control keywords
|
|
72
|
+
are now excluded from method-name matches; the real private-method gate finding
|
|
73
|
+
stays covered.
|
|
74
|
+
- **`SEC-007` reports JavaMelody descriptor credentials in their field shape.**
|
|
75
|
+
`authorized-users` is now read directly from the named descriptor parameter even
|
|
76
|
+
when the value carries a digest (`{SHA-256}…`), spans multiple lines, or is
|
|
77
|
+
wrapped in `CDATA`, and the generic descriptor-value fallback is deduplicated so
|
|
78
|
+
one credential yields exactly one finding. The evidence line stays redacted.
|
|
79
|
+
|
|
80
|
+
### Tests
|
|
81
|
+
|
|
82
|
+
- Regression coverage binds both fixes through the full `spring-audit` CLI path,
|
|
83
|
+
including a `options/DPA/WEB-INF/web.xml` descriptor and a controller whose
|
|
84
|
+
handler body contains the control constructs that produced the false positive.
|
|
85
|
+
|
|
5
86
|
## [4.10.2] — 2026-08-05
|
|
6
87
|
|
|
7
88
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.10.
|
|
3
|
+
Version: 4.10.4
|
|
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
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Classifier: Topic :: Utilities
|
|
19
19
|
Requires-Python: >=3.9
|
|
20
20
|
Requires-Dist: defusedxml>=0.7
|
|
21
|
-
Requires-Dist: mcp
|
|
21
|
+
Requires-Dist: mcp<2,>=1.2
|
|
22
22
|
Requires-Dist: pathspec>=1.0
|
|
23
23
|
Requires-Dist: ruamel-yaml>=0.18
|
|
24
24
|
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
@@ -28,12 +28,12 @@ Requires-Dist: tree-sitter-javascript>=0.21; extra == 'ast'
|
|
|
28
28
|
Requires-Dist: tree-sitter-typescript>=0.21; extra == 'ast'
|
|
29
29
|
Requires-Dist: tree-sitter>=0.21; extra == 'ast'
|
|
30
30
|
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: mcp
|
|
31
|
+
Requires-Dist: mcp<2,>=1.2; extra == 'dev'
|
|
32
32
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
33
33
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
34
34
|
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
35
35
|
Provides-Extra: mcp
|
|
36
|
-
Requires-Dist: mcp
|
|
36
|
+
Requires-Dist: mcp<2,>=1.2; extra == 'mcp'
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
|
|
39
39
|
# ASK Engine
|
|
@@ -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`**
|
|
@@ -106,7 +106,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
106
106
|
# pip / pipx
|
|
107
107
|
pipx install sourcecode # or: pip install sourcecode
|
|
108
108
|
|
|
109
|
-
ask version # ask 4.10.
|
|
109
|
+
ask version # ask 4.10.4 — and, on a build that has aged,
|
|
110
110
|
# how many releases have probably shipped since
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -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`**
|
|
@@ -68,7 +68,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
68
68
|
# pip / pipx
|
|
69
69
|
pipx install sourcecode # or: pip install sourcecode
|
|
70
70
|
|
|
71
|
-
ask version # ask 4.10.
|
|
71
|
+
ask version # ask 4.10.4 — and, on a build that has aged,
|
|
72
72
|
# how many releases have probably shipped since
|
|
73
73
|
```
|
|
74
74
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sourcecode"
|
|
7
|
-
version = "4.10.
|
|
7
|
+
version = "4.10.4"
|
|
8
8
|
description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"pathspec>=1.0",
|
|
31
31
|
"ruamel.yaml>=0.18",
|
|
32
32
|
"tomli>=2.0; python_version < '3.11'",
|
|
33
|
-
"mcp>=1.
|
|
33
|
+
"mcp>=1.2,<2",
|
|
34
34
|
"defusedxml>=0.7",
|
|
35
35
|
]
|
|
36
36
|
|
|
@@ -43,7 +43,7 @@ dev = [
|
|
|
43
43
|
"pytest>=8",
|
|
44
44
|
"ruff>=0.15",
|
|
45
45
|
"mypy>=1.10",
|
|
46
|
-
"mcp>=1.
|
|
46
|
+
"mcp>=1.2,<2",
|
|
47
47
|
]
|
|
48
48
|
ast = [
|
|
49
49
|
"tree-sitter>=0.21",
|
|
@@ -51,7 +51,7 @@ ast = [
|
|
|
51
51
|
"tree-sitter-javascript>=0.21",
|
|
52
52
|
]
|
|
53
53
|
mcp = [
|
|
54
|
-
"mcp>=1.
|
|
54
|
+
"mcp>=1.2,<2",
|
|
55
55
|
]
|
|
56
56
|
|
|
57
57
|
[[tool.mypy.overrides]]
|
|
@@ -33,7 +33,7 @@ import json
|
|
|
33
33
|
import subprocess
|
|
34
34
|
from datetime import datetime, timezone
|
|
35
35
|
from pathlib import Path
|
|
36
|
-
from typing import TYPE_CHECKING
|
|
36
|
+
from typing import TYPE_CHECKING, Any
|
|
37
37
|
|
|
38
38
|
from sourcecode import __version__ as TOOL_VERSION
|
|
39
39
|
from sourcecode.architectural_delta import ArchMetrics, diff_metrics, extract_metrics
|
|
@@ -107,6 +107,35 @@ def _hotspots(fan_in: dict[str, int], top_n: int) -> list[dict]:
|
|
|
107
107
|
return [{"symbol": s, "fan_in": c} for s, c in ranked[:top_n]]
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def _fan_in_to_list(fan_in: dict[str, int]) -> list[dict]:
|
|
111
|
+
"""Serialise the full fan-in map as an array of ``{symbol, fan_in}`` objects,
|
|
112
|
+
sorted by symbol — never a JSON object keyed by symbol.
|
|
113
|
+
|
|
114
|
+
C3-62: PowerShell's `ConvertFrom-Json` folds object keys case-insensitively, so
|
|
115
|
+
two distinct Java symbols differing only by case (legal — Java is case-sensitive)
|
|
116
|
+
collapse into one key and abort parsing the *whole* baseline document. `hotspots`
|
|
117
|
+
already used this array shape (`_hotspots`); the full map stored beside it did
|
|
118
|
+
not. An array of objects has no such ceiling on any consumer."""
|
|
119
|
+
return [{"symbol": s, "fan_in": c} for s, c in sorted(fan_in.items())]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _fan_in_from_stored(value: Any) -> dict[str, int]:
|
|
123
|
+
"""Read a stored ``fan_in`` field back into a symbol → count map.
|
|
124
|
+
|
|
125
|
+
Accepts both the array-of-objects shape (C3-62, current) and the legacy
|
|
126
|
+
object-keyed-by-symbol shape (baselines captured before this fix) so an older
|
|
127
|
+
baseline already committed to disk keeps diffing and trending correctly."""
|
|
128
|
+
if isinstance(value, list):
|
|
129
|
+
return {
|
|
130
|
+
str(item["symbol"]): int(item.get("fan_in", 0))
|
|
131
|
+
for item in value
|
|
132
|
+
if isinstance(item, dict) and "symbol" in item
|
|
133
|
+
}
|
|
134
|
+
if isinstance(value, dict):
|
|
135
|
+
return {str(k): int(v) for k, v in value.items()}
|
|
136
|
+
return {}
|
|
137
|
+
|
|
138
|
+
|
|
110
139
|
def build_baseline(
|
|
111
140
|
cir: "CanonicalRepositoryIR",
|
|
112
141
|
*,
|
|
@@ -126,7 +155,6 @@ def build_baseline(
|
|
|
126
155
|
that share them."""
|
|
127
156
|
m = extract_metrics(cir)
|
|
128
157
|
endpoints = sorted([meth, path] for (meth, path) in m.endpoints)
|
|
129
|
-
fan_in = dict(sorted(m.fan_in.items()))
|
|
130
158
|
cycles = sorted((sorted(c) for c in m.cycles), key=lambda mm: (len(mm), mm))
|
|
131
159
|
return {
|
|
132
160
|
"schema": ARCH_BASELINE_SCHEMA,
|
|
@@ -145,7 +173,7 @@ def build_baseline(
|
|
|
145
173
|
},
|
|
146
174
|
"hotspots": _hotspots(m.fan_in, hotspot_n),
|
|
147
175
|
"endpoint_surface": endpoints,
|
|
148
|
-
"fan_in": fan_in,
|
|
176
|
+
"fan_in": _fan_in_to_list(m.fan_in),
|
|
149
177
|
"import_cycles": cycles,
|
|
150
178
|
"env": env if env is not None else collect_env(),
|
|
151
179
|
"provenance": (
|
|
@@ -209,7 +237,7 @@ def baseline_to_metrics(baseline: dict) -> ArchMetrics:
|
|
|
209
237
|
endpoint_count=int(totals.get("endpoints", 0)),
|
|
210
238
|
dependency_edge_count=int(totals.get("dependency_edges", 0)),
|
|
211
239
|
endpoints=endpoints,
|
|
212
|
-
fan_in=
|
|
240
|
+
fan_in=_fan_in_from_stored(baseline.get("fan_in")),
|
|
213
241
|
cycles=frozenset(
|
|
214
242
|
frozenset(str(m) for m in members)
|
|
215
243
|
for members in baseline.get("import_cycles", [])
|
|
@@ -397,9 +425,10 @@ def build_trend(baselines: list[dict]) -> dict:
|
|
|
397
425
|
if isinstance(h, dict) and h.get("symbol"):
|
|
398
426
|
tracked.add(str(h["symbol"]))
|
|
399
427
|
|
|
428
|
+
fan_in_by_baseline = [_fan_in_from_stored(b.get("fan_in")) for b in baselines]
|
|
400
429
|
hotspot_trends: list[dict] = []
|
|
401
430
|
for sym in sorted(tracked):
|
|
402
|
-
series = [
|
|
431
|
+
series = [fmap.get(sym, 0) for fmap in fan_in_by_baseline]
|
|
403
432
|
first, last = series[0], series[-1]
|
|
404
433
|
hotspot_trends.append({
|
|
405
434
|
"symbol": sym,
|
|
@@ -302,8 +302,48 @@ def render_markdown() -> str:
|
|
|
302
302
|
return "\n".join(out)
|
|
303
303
|
|
|
304
304
|
|
|
305
|
+
def render_summary() -> str:
|
|
306
|
+
"""The model as `ask cache model` prints it by default (C3-63).
|
|
307
|
+
|
|
308
|
+
One line per layer and one line per command — every command named (nothing
|
|
309
|
+
curated out, so the same completeness the battery checks against `render_text`
|
|
310
|
+
holds here too), but without the per-command prose (`note`) and timing
|
|
311
|
+
(`measured`) detail that made the default ~2 500 tokens of prose in a CLI whose
|
|
312
|
+
own selling argument is token economy. That detail is still one flag away.
|
|
313
|
+
"""
|
|
314
|
+
lines: list[str] = []
|
|
315
|
+
lines.append(
|
|
316
|
+
"Invalidation: every layer keys on the exact tree state — any change to the "
|
|
317
|
+
"analysed files invalidates it, committed or not."
|
|
318
|
+
)
|
|
319
|
+
lines.append("")
|
|
320
|
+
lines.append("Layers")
|
|
321
|
+
id_width = max(len(lyr.id) for lyr in LAYERS)
|
|
322
|
+
for lyr in LAYERS:
|
|
323
|
+
lines.append(f" {lyr.id.ljust(id_width)} {lyr.stores}")
|
|
324
|
+
lines.append("")
|
|
325
|
+
lines.append(f"What a warm gives each command ({REFERENCE_REPOSITORY})")
|
|
326
|
+
cmd_width = max(len(c.command) for c in COMMANDS)
|
|
327
|
+
warm_width = max(len(label) for label in _WARM_LABEL.values())
|
|
328
|
+
for cmd in COMMANDS:
|
|
329
|
+
repeat = "repeat cached" if cmd.repeat else "recomputes"
|
|
330
|
+
layers = ", ".join(cmd.reads) or "none"
|
|
331
|
+
lines.append(
|
|
332
|
+
f" {cmd.command.ljust(cmd_width)} {_WARM_LABEL[cmd.warm].ljust(warm_width)} "
|
|
333
|
+
f"({repeat}) [{layers}]"
|
|
334
|
+
)
|
|
335
|
+
lines.append("")
|
|
336
|
+
lines.append(
|
|
337
|
+
"Full detail (measured timings, per-command notes): ask cache model --full"
|
|
338
|
+
)
|
|
339
|
+
lines.append("Tables for docs: ask cache model --markdown · machine-readable: --json")
|
|
340
|
+
return "\n".join(lines)
|
|
341
|
+
|
|
342
|
+
|
|
305
343
|
def render_text() -> str:
|
|
306
|
-
"""The model
|
|
344
|
+
"""The model in full: every layer and command with its measured timing and note.
|
|
345
|
+
|
|
346
|
+
`ask cache model --full`. See `render_summary` for the default."""
|
|
307
347
|
lines: list[str] = []
|
|
308
348
|
lines.append("Invalidation — one rule for every layer")
|
|
309
349
|
lines.append(" Every layer keys on the exact tree state. Any change to the analysed files")
|
|
@@ -15,7 +15,7 @@ from typing import Any, NoReturn, Optional, cast
|
|
|
15
15
|
import typer
|
|
16
16
|
|
|
17
17
|
from sourcecode import __version__
|
|
18
|
-
from sourcecode.error_schema import INVALID_INPUT_CODE, build_error_envelope
|
|
18
|
+
from sourcecode.error_schema import EXECUTION_FAILED_CODE, INVALID_INPUT_CODE, build_error_envelope
|
|
19
19
|
from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
|
|
20
20
|
from sourcecode.progress import Progress
|
|
21
21
|
from sourcecode import perf
|
|
@@ -7402,7 +7402,14 @@ def spring_audit_cmd(
|
|
|
7402
7402
|
except Exception:
|
|
7403
7403
|
pass
|
|
7404
7404
|
finally:
|
|
7405
|
-
|
|
7405
|
+
# C3-54: `done` must not claim the answer exists before it does. The
|
|
7406
|
+
# field log showed `[ask] done (6.8m)` printed, then the process torn
|
|
7407
|
+
# down before `-o` was written — a false success on 6.8 minutes of
|
|
7408
|
+
# CPU. `stop()` here only clears the spinner (satisfies the standing
|
|
7409
|
+
# rule that a raised error must never print under a live spinner
|
|
7410
|
+
# frame); it prints no completion line. `_prog.finish()` runs below,
|
|
7411
|
+
# after the render AND the write have actually happened.
|
|
7412
|
+
_prog.stop()
|
|
7406
7413
|
if table:
|
|
7407
7414
|
output = _render_spring_audit_table(
|
|
7408
7415
|
combined, rule_filter=rule_filter, top_n=top_n, min_severity=min_severity
|
|
@@ -7415,6 +7422,7 @@ def spring_audit_cmd(
|
|
|
7415
7422
|
_total = combined.summary.get("total_findings", 0)
|
|
7416
7423
|
_emit_command_output(output, output_path, copy,
|
|
7417
7424
|
success_msg=f"Spring audit written to {output_path} ({_total} findings)")
|
|
7425
|
+
_prog.finish()
|
|
7418
7426
|
|
|
7419
7427
|
if ci and combined.findings:
|
|
7420
7428
|
raise typer.Exit(code=1)
|
|
@@ -11569,12 +11577,33 @@ def mcp_status(copy: bool = _copy_option()) -> None:
|
|
|
11569
11577
|
_answer.say("")
|
|
11570
11578
|
|
|
11571
11579
|
# Stage 1: Dependencies
|
|
11580
|
+
# C3-57: check the symbol this CLI actually consumes (FastMCP), not just
|
|
11581
|
+
# whether the root `mcp` package imports — `mcp>=2.0` imports cleanly but
|
|
11582
|
+
# has no `mcp.server.fastmcp` module, so importing only the root package
|
|
11583
|
+
# reports a green check for a build that will fail on `mcp serve`/`mcp list-tools`.
|
|
11584
|
+
def _installed_mcp_version() -> str:
|
|
11585
|
+
# The `mcp` package does not set `__version__`; read installed
|
|
11586
|
+
# distribution metadata instead, and fall back gracefully off a
|
|
11587
|
+
# normal install (editable installs, vendored copies, etc.).
|
|
11588
|
+
try:
|
|
11589
|
+
from importlib.metadata import version as _dist_version
|
|
11590
|
+
return _dist_version("mcp")
|
|
11591
|
+
except Exception:
|
|
11592
|
+
return "unknown"
|
|
11593
|
+
|
|
11572
11594
|
try:
|
|
11573
|
-
|
|
11574
|
-
|
|
11595
|
+
from mcp.server.fastmcp import FastMCP # noqa: F401
|
|
11596
|
+
_mcp_ver = _installed_mcp_version()
|
|
11597
|
+
_answer.say(f"Dependencies ✓ installed (mcp {_mcp_ver})")
|
|
11575
11598
|
except ImportError:
|
|
11576
|
-
|
|
11577
|
-
|
|
11599
|
+
try:
|
|
11600
|
+
import mcp as _mcp_pkg # noqa: F401
|
|
11601
|
+
_mcp_ver = _installed_mcp_version()
|
|
11602
|
+
_answer.say(f"Dependencies ✗ mcp {_mcp_ver} incompatible (requires mcp>=1.2,<2)")
|
|
11603
|
+
_answer.say(" Fix: pip install 'sourcecode[mcp]' --upgrade")
|
|
11604
|
+
except ImportError:
|
|
11605
|
+
_answer.say("Dependencies ✗ missing")
|
|
11606
|
+
_answer.say(" Fix: pip install sourcecode[mcp]")
|
|
11578
11607
|
_answer.say("")
|
|
11579
11608
|
|
|
11580
11609
|
clients = detect_clients()
|
|
@@ -11789,9 +11818,32 @@ def mcp_list_tools(
|
|
|
11789
11818
|
import asyncio
|
|
11790
11819
|
import json as _json
|
|
11791
11820
|
|
|
11792
|
-
|
|
11821
|
+
# C3-57: this command previously let a raw Python traceback through when
|
|
11822
|
+
# the installed `mcp` package was incompatible (e.g. mcp>=2.0, which has
|
|
11823
|
+
# no mcp.server.fastmcp) — the only command in the catalogue to do so.
|
|
11824
|
+
# Route failures through the same structured error contract as every
|
|
11825
|
+
# other command instead.
|
|
11826
|
+
try:
|
|
11827
|
+
from sourcecode.mcp.server import mcp as _mcp
|
|
11828
|
+
tools = asyncio.run(_mcp.list_tools())
|
|
11829
|
+
except ImportError as exc:
|
|
11830
|
+
_emit_error_json(
|
|
11831
|
+
EXECUTION_FAILED_CODE,
|
|
11832
|
+
f"MCP server dependencies are not importable: {exc}",
|
|
11833
|
+
hint="Run 'ask mcp status' to check the installed mcp package version — "
|
|
11834
|
+
"this CLI requires mcp>=1.2,<2. Fix: pip install 'sourcecode[mcp]' --upgrade",
|
|
11835
|
+
expected="mcp>=1.2,<2 installed with mcp.server.fastmcp.FastMCP importable.",
|
|
11836
|
+
)
|
|
11837
|
+
raise typer.Exit(code=1)
|
|
11838
|
+
except Exception as exc:
|
|
11839
|
+
_emit_error_json(
|
|
11840
|
+
EXECUTION_FAILED_CODE,
|
|
11841
|
+
f"Failed to list MCP tools: {type(exc).__name__}: {exc}",
|
|
11842
|
+
hint="Run 'ask mcp status' to check MCP server health.",
|
|
11843
|
+
expected="A list of registered MCP tools.",
|
|
11844
|
+
)
|
|
11845
|
+
raise typer.Exit(code=1)
|
|
11793
11846
|
|
|
11794
|
-
tools = asyncio.run(_mcp.list_tools())
|
|
11795
11847
|
tools_sorted = sorted(tools, key=lambda t: t.name)
|
|
11796
11848
|
|
|
11797
11849
|
if json_output:
|
|
@@ -12057,6 +12109,11 @@ def cache_model_cmd(
|
|
|
12057
12109
|
path: Path = typer.Argument(Path("."), help="Repository path for CLI consistency (model is global)."),
|
|
12058
12110
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
12059
12111
|
markdown: bool = typer.Option(False, "--markdown", help="Output the tables published in the user guide."),
|
|
12112
|
+
full: bool = typer.Option(
|
|
12113
|
+
False, "--full",
|
|
12114
|
+
help="Full prose: every command's measured timing and note (~2.5k tokens). "
|
|
12115
|
+
"Default is a compact one-line-per-command summary.",
|
|
12116
|
+
),
|
|
12060
12117
|
copy: bool = _copy_option(),
|
|
12061
12118
|
) -> None:
|
|
12062
12119
|
"""What each cache layer stores, what invalidates it, and what a warm helps.
|
|
@@ -12070,6 +12127,10 @@ def cache_model_cmd(
|
|
|
12070
12127
|
|
|
12071
12128
|
One rule covers invalidation: every layer keys on the exact tree state, so any
|
|
12072
12129
|
change to the analysed files invalidates it, committed or not.
|
|
12130
|
+
|
|
12131
|
+
\b
|
|
12132
|
+
Default output is a compact summary (C3-63) — every command named, without the
|
|
12133
|
+
per-command measured timing and note. Pass --full for that detail.
|
|
12073
12134
|
"""
|
|
12074
12135
|
from sourcecode import cache_model as _cmodel
|
|
12075
12136
|
|
|
@@ -12089,7 +12150,8 @@ def cache_model_cmd(
|
|
|
12089
12150
|
f"Scope: {Path(path).resolve()} (model is global; path accepted for "
|
|
12090
12151
|
"cache command grammar consistency)\n\n"
|
|
12091
12152
|
)
|
|
12092
|
-
|
|
12153
|
+
_body = _cmodel.render_text() if full else _cmodel.render_summary()
|
|
12154
|
+
_emit_text_output(_prefix + _body, copy)
|
|
12093
12155
|
|
|
12094
12156
|
|
|
12095
12157
|
@cache_app.command("freshness", short_help="RIS freshness relative to the current git HEAD")
|
|
@@ -105,8 +105,6 @@ def _claims_from_rows(command: str, payload: dict[str, Any]) -> list[dict[str, A
|
|
|
105
105
|
if row.get("rule_ids"):
|
|
106
106
|
claim["rule_ids"] = row["rule_ids"]
|
|
107
107
|
claims.append(claim)
|
|
108
|
-
if len(claims) >= _MAX_CLAIMS:
|
|
109
|
-
return claims
|
|
110
108
|
return claims
|
|
111
109
|
|
|
112
110
|
|
|
@@ -166,9 +164,16 @@ def build_evidence_manifest(
|
|
|
166
164
|
) -> dict[str, Any]:
|
|
167
165
|
"""Build a machine-readable provenance index for an existing payload."""
|
|
168
166
|
root = Path(root).resolve()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
# I-2: measure the whole population, THEN cut it. `_claims_from_rows` used to
|
|
168
|
+
# return the moment it reached the cap, so the number of claims the payload
|
|
169
|
+
# actually carries was never computed and could not be published — the manifest
|
|
170
|
+
# reported the cap itself as `total_indexed`, which is the exact shape ("the
|
|
171
|
+
# published count is the size of the rendering") this module exists to make
|
|
172
|
+
# auditable. Building every claim is arithmetic over rows already in memory.
|
|
173
|
+
all_claims = _claims_from_rows(command, payload)
|
|
174
|
+
all_claims.extend(_claims_from_java_lts(command, payload))
|
|
175
|
+
total_claims = len(all_claims)
|
|
176
|
+
claims = all_claims[:_MAX_CLAIMS]
|
|
172
177
|
sources = sorted({
|
|
173
178
|
str((claim.get("source") or {}).get("path"))
|
|
174
179
|
for claim in claims
|
|
@@ -189,11 +194,14 @@ def build_evidence_manifest(
|
|
|
189
194
|
"run": run,
|
|
190
195
|
"claims": claims,
|
|
191
196
|
"sources": [{"path": path} for path in sources],
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
# `total` is the measurement; `total_indexed` is the size of this manifest.
|
|
198
|
+
# `capped` is strictly `total > cap`: a payload with exactly `_MAX_CLAIMS`
|
|
199
|
+
# claims had nothing removed and must not report that it did.
|
|
200
|
+
"claims_cap": {
|
|
201
|
+
"total": total_claims,
|
|
202
|
+
"total_indexed": min(total_claims, _MAX_CLAIMS),
|
|
203
|
+
"capped": total_claims > _MAX_CLAIMS,
|
|
204
|
+
},
|
|
197
205
|
"embedded_manifests": _embedded_manifests(payload),
|
|
198
206
|
"limits": {
|
|
199
207
|
"statement": (
|
|
@@ -27,7 +27,7 @@ from typing import Any, Optional
|
|
|
27
27
|
#: The date `__version__` was released, from that version's CHANGELOG entry.
|
|
28
28
|
#: `tests/test_release_info.py` fails the build if the two disagree, so this is
|
|
29
29
|
#: a copy of a fact rather than a second authority for it.
|
|
30
|
-
RELEASE_DATE = "2026-08-
|
|
30
|
+
RELEASE_DATE = "2026-08-07"
|
|
31
31
|
|
|
32
32
|
#: Releases per day, measured over the 30 releases before this one (the same
|
|
33
33
|
#: computation the test re-runs against the CHANGELOG). Stable across window
|
|
@@ -111,8 +111,8 @@ _IS_REFERENCE = re.compile(r"[$@]\{[^}]+\}|^\s*$|^\s*ENC\(|^\s*\*+\s*$")
|
|
|
111
111
|
#: XML elements whose text is a configured VALUE. Servlet/JEE spec vocabulary.
|
|
112
112
|
_DESCRIPTOR_VALUE = re.compile(
|
|
113
113
|
r"<(?P<tag>param-value|env-entry-value|value|property|password)\b[^>]*>"
|
|
114
|
-
r"(?P<value
|
|
115
|
-
re.IGNORECASE,
|
|
114
|
+
r"(?P<value>.*?)</(?P=tag)>",
|
|
115
|
+
re.IGNORECASE | re.DOTALL,
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
_DESCRIPTOR_INIT_PARAM = re.compile(
|
|
@@ -124,8 +124,8 @@ _DESCRIPTOR_PARAM_NAME = re.compile(
|
|
|
124
124
|
re.IGNORECASE,
|
|
125
125
|
)
|
|
126
126
|
_DESCRIPTOR_PARAM_VALUE = re.compile(
|
|
127
|
-
r"<param-value\b[^>]*>(?P<value
|
|
128
|
-
re.IGNORECASE,
|
|
127
|
+
r"<param-value\b[^>]*>(?P<value>.*?)</param-value>",
|
|
128
|
+
re.IGNORECASE | re.DOTALL,
|
|
129
129
|
)
|
|
130
130
|
_JAVAMELODY_CREDENTIAL_PARAMS: frozenset[str] = frozenset({
|
|
131
131
|
"authorized-users",
|
|
@@ -364,7 +364,14 @@ def _scan_descriptor(text: str, rel: str) -> "list[SecurityConfigObservation]":
|
|
|
364
364
|
holds a reference and is not reported; one that carries a digest literal
|
|
365
365
|
holds the credential itself, in a file under version control.
|
|
366
366
|
"""
|
|
367
|
+
def _xml_value(raw: str) -> str:
|
|
368
|
+
value = raw.strip()
|
|
369
|
+
if value.startswith("<![CDATA[") and value.endswith("]]>"):
|
|
370
|
+
value = value[len("<![CDATA["):-len("]]>")]
|
|
371
|
+
return value.strip()
|
|
372
|
+
|
|
367
373
|
out: "list[SecurityConfigObservation]" = []
|
|
374
|
+
javamelody_value_offsets: set[int] = set()
|
|
368
375
|
for param in _DESCRIPTOR_INIT_PARAM.finditer(text):
|
|
369
376
|
body = param.group("body")
|
|
370
377
|
name_m = _DESCRIPTOR_PARAM_NAME.search(body)
|
|
@@ -372,22 +379,29 @@ def _scan_descriptor(text: str, rel: str) -> "list[SecurityConfigObservation]":
|
|
|
372
379
|
if name_m is None or value_m is None:
|
|
373
380
|
continue
|
|
374
381
|
name = name_m.group("value").strip().lower()
|
|
375
|
-
value = value_m.group("value")
|
|
382
|
+
value = _xml_value(value_m.group("value"))
|
|
376
383
|
if name not in _JAVAMELODY_CREDENTIAL_PARAMS:
|
|
377
384
|
continue
|
|
378
385
|
if not value or _IS_REFERENCE.search(value):
|
|
379
386
|
continue
|
|
380
387
|
if ":" not in value:
|
|
381
388
|
continue
|
|
382
|
-
if _DIGEST_LITERAL.search(value):
|
|
383
|
-
continue
|
|
384
389
|
user = value.split(":", 1)[0].strip() or "user"
|
|
390
|
+
value_offset = param.start("body") + value_m.start("value")
|
|
391
|
+
javamelody_value_offsets.add(value_offset)
|
|
392
|
+
digest = _DIGEST_LITERAL.search(value)
|
|
393
|
+
evidence = (
|
|
394
|
+
f"<param-value>{user}:…{{{digest.group('algo')}}}"
|
|
395
|
+
f"{digest.group('digest')[:8]}…</param-value>"
|
|
396
|
+
if digest is not None
|
|
397
|
+
else f"<param-value>{user}:…</param-value>"
|
|
398
|
+
)
|
|
385
399
|
out.append(SecurityConfigObservation(
|
|
386
400
|
rule="SEC-007",
|
|
387
401
|
file=rel,
|
|
388
|
-
line=_line_of(text,
|
|
402
|
+
line=_line_of(text, value_offset),
|
|
389
403
|
symbol=rel,
|
|
390
|
-
evidence=
|
|
404
|
+
evidence=evidence,
|
|
391
405
|
detail=(
|
|
392
406
|
"JavaMelody authorized-users stores a monitoring-console credential "
|
|
393
407
|
"directly in the deployment descriptor. The value is redacted here; "
|
|
@@ -395,7 +409,9 @@ def _scan_descriptor(text: str, rel: str) -> "list[SecurityConfigObservation]":
|
|
|
395
409
|
),
|
|
396
410
|
))
|
|
397
411
|
for match in _DESCRIPTOR_VALUE.finditer(text):
|
|
398
|
-
|
|
412
|
+
if match.start("value") in javamelody_value_offsets:
|
|
413
|
+
continue
|
|
414
|
+
value = _xml_value(match.group("value"))
|
|
399
415
|
if not value or _IS_REFERENCE.search(value):
|
|
400
416
|
continue
|
|
401
417
|
digest = _DIGEST_LITERAL.search(value)
|
|
@@ -508,7 +508,8 @@ def _line_of(source: str, offset: int) -> int:
|
|
|
508
508
|
_JAVA_METHOD_RE = re.compile(
|
|
509
509
|
r"(?P<mods>(?:(?:public|protected|private|static|final|synchronized|abstract|native)\s+)*)"
|
|
510
510
|
r"(?P<ret>[\w.$<>\[\], ?]+)\s+"
|
|
511
|
-
r"(?P<name>
|
|
511
|
+
r"(?P<name>(?!(?:if|for|while|switch|catch|synchronized|return|new|throw|try|else|do)\b)"
|
|
512
|
+
r"[a-zA-Z_][\w$]*)\s*\([^;{}]*\)\s*(?:throws\s+[^{]+)?\{",
|
|
512
513
|
re.MULTILINE,
|
|
513
514
|
)
|
|
514
515
|
|
|
@@ -620,6 +621,14 @@ class _GATE00XCustomGateAudit:
|
|
|
620
621
|
findings: list[SpringFinding] = []
|
|
621
622
|
ann_re = _annotation_call_re(token)
|
|
622
623
|
for rel, source in sources:
|
|
624
|
+
# C3-61: `ann_re` is `@<token>\b…`, so it cannot match a source that does
|
|
625
|
+
# not contain the literal token, and neither can any prefix slice of it.
|
|
626
|
+
# Without this line every source pays a full method `finditer` plus one
|
|
627
|
+
# prefix regex per method before proving it had nothing to find — 98,9 %
|
|
628
|
+
# of (source, token) pairs on the measured repository. Same guard
|
|
629
|
+
# `_advice_body_rules` already carries.
|
|
630
|
+
if token not in source:
|
|
631
|
+
continue
|
|
623
632
|
class_fqn = _java_fqn(source)
|
|
624
633
|
annotated_methods: dict[str, tuple[int, str]] = {}
|
|
625
634
|
for method in _JAVA_METHOD_RE.finditer(source):
|
|
@@ -711,6 +720,8 @@ class _GATE00XCustomGateAudit:
|
|
|
711
720
|
findings: list[SpringFinding] = []
|
|
712
721
|
ann_re = _annotation_call_re(token)
|
|
713
722
|
for rel, source in sources:
|
|
723
|
+
if token not in source: # C3-61, same equivalence as `_proxy_bypass`
|
|
724
|
+
continue
|
|
714
725
|
class_fqn = _java_fqn(source)
|
|
715
726
|
for ann in ann_re.finditer(source):
|
|
716
727
|
args = (ann.group("args") or "").strip()
|