sourcecode 4.10.3__tar.gz → 4.10.5__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.3 → sourcecode-4.10.5}/CHANGELOG.md +122 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/PKG-INFO +6 -6
- {sourcecode-4.10.3 → sourcecode-4.10.5}/README.md +2 -2
- {sourcecode-4.10.3 → sourcecode-4.10.5}/pyproject.toml +4 -4
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architectural_baseline.py +34 -5
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cache_model.py +41 -1
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cli.py +121 -18
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pr_impact.py +38 -4
- sourcecode-4.10.5/src/sourcecode/progress.py +218 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/provenance.py +18 -10
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/release_info.py +1 -1
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/repository_ir.py +38 -1
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_config_scan.py +30 -22
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_security_audit.py +10 -0
- sourcecode-4.10.5/src/sourcecode/text_input.py +93 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_edit.py +31 -0
- sourcecode-4.10.3/src/sourcecode/progress.py +0 -125
- {sourcecode-4.10.3 → sourcecode-4.10.5}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/.gitignore +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/.ruff.toml +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/CLAUDE.md +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/CONTRIBUTING.md +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/LICENSE +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/SECURITY.md +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/raw +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/perf_harness.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/license.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/runs.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/source_text.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/README.md +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,128 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.10.5] — 2026-08-07
|
|
6
|
+
|
|
7
|
+
Second release of the field-evaluation #13 queue. Three rows close and one closes
|
|
8
|
+
in part; the suite is 6 691 tests green.
|
|
9
|
+
|
|
10
|
+
⚠ **Two behaviour changes to read before upgrading a pipeline.** Both are the fix
|
|
11
|
+
working, and both can move a gate:
|
|
12
|
+
|
|
13
|
+
- On Windows, `pr-impact --files -` now resolves paths it previously mangled, so a
|
|
14
|
+
pipeline that was passing because its diff decoded to nothing can start failing —
|
|
15
|
+
with the verdict the diff always deserved (C3-58).
|
|
16
|
+
- A security finding that existed only in build output (`target/classes`) is no
|
|
17
|
+
longer reported, so a repository audited mid-build can report fewer findings
|
|
18
|
+
(C3-64).
|
|
19
|
+
|
|
20
|
+
### Performance
|
|
21
|
+
|
|
22
|
+
- **The security configuration scan discovers its files 8.7× faster** (C3-64):
|
|
23
|
+
0.61 s → 0.07 s on keycloak, 0.45 s → 0.05 s on alfresco. Five `rglob` calls
|
|
24
|
+
became one pruned walk. `rglob` cannot skip a subtree, so the old cost was set by
|
|
25
|
+
the build output and the VCS metadata rather than by the source: 48.4 % of
|
|
26
|
+
openmrs-core's tree entries and 53.3 % of this repository's live under directories
|
|
27
|
+
that hold no source at all. On a clean checkout the end-to-end scan moves 1.73 s →
|
|
28
|
+
1.43 s (the rest is reading Java sources); the win grows with how built the tree
|
|
29
|
+
is, which is the state a developer's repository is normally in.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **A path list is decoded by its bytes, never by the platform locale** (C3-58).
|
|
34
|
+
`ask pr-impact --files -` read stdin through Python's locale decoding, which on
|
|
35
|
+
Windows is the ANSI codepage: `git diff --name-only | ask pr-impact --files -`
|
|
36
|
+
returned `risk: UNKNOWN` and 0 endpoints where the same list without the shell's
|
|
37
|
+
default byte-order mark returned `CRITICAL` and 1 144 — a green pipeline on the
|
|
38
|
+
gating command. The BOM was the visible half; `AñoService.java` arriving as
|
|
39
|
+
`AñoService.java` was the cause. The new authority `sourcecode/text_input.py`
|
|
40
|
+
reads `sys.stdin.buffer` and lets the bytes name the encoding, and the list-file
|
|
41
|
+
shape goes through it too: PowerShell 5.1 writes UTF-16-LE by default, which the
|
|
42
|
+
previous UTF-8 read raised on. Undecodable bytes now yield an unusable path with
|
|
43
|
+
encoding named as a cause, never a traceback.
|
|
44
|
+
- **No command claims `done` before its answer exists** (C3-54, part 2). The
|
|
45
|
+
completion line moved out of `Progress.finish()` entirely: `finish()` claims the
|
|
46
|
+
work is done, and the line is printed when the answer has actually been handed
|
|
47
|
+
over. A run killed in between prints nothing, and a run that raised prints
|
|
48
|
+
nothing — previously ~20 commands emitted `done (Xs)` while their `--output` file
|
|
49
|
+
did not exist yet, which in the field meant 6.8 minutes of analysis reported as
|
|
50
|
+
success over an absent file. Output files are also durable now: the shared writer
|
|
51
|
+
fsyncs data and directory entry before anything reports a write, and `repo-ir` —
|
|
52
|
+
the one `-o` path that bypassed that writer — goes through it.
|
|
53
|
+
- **A rejected progress write no longer silences progress for the rest of the run**
|
|
54
|
+
(C3-56, part). The heartbeat treated any write failure as terminal; it now retries
|
|
55
|
+
and gives up only after five consecutive failures. The reported 393 s of silence
|
|
56
|
+
did not reproduce: the cadence is timer-driven and measured exact on a cold
|
|
57
|
+
29.9 s run (5.0 / 10.0 / 15.0 / 20.0 / 25.1 s). The progress denominator and ETA
|
|
58
|
+
the row also asks for are still open.
|
|
59
|
+
- **A build copy is no longer a second security finding** (C3-64). The configuration
|
|
60
|
+
scan walked the whole tree five times and could not skip a subtree, so a compiled
|
|
61
|
+
copy of `application.properties` under `target/classes` was reported beside the
|
|
62
|
+
source file it came from. It now walks once, pruning build output and VCS metadata
|
|
63
|
+
with the same ignore set Java discovery uses.
|
|
64
|
+
- **An unresolved changed file names the right thing to check** (C3-58). One message
|
|
65
|
+
covered both causes and pointed at the wrong one: a path that is *not on disk* is a
|
|
66
|
+
path problem (form and encoding), while a path that *is* there but maps to no class
|
|
67
|
+
is a fact about the file. They now say so separately.
|
|
68
|
+
|
|
69
|
+
## [4.10.4] — 2026-08-07
|
|
70
|
+
|
|
71
|
+
First release of the queue opened by field evaluation #13. Four of its rows close,
|
|
72
|
+
one closes largely, and the suite goes green: `master` was carrying four failing
|
|
73
|
+
tests before this release, none of them noticed by the release that shipped over
|
|
74
|
+
them.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **`baseline` no longer emits a JSON object keyed by symbol** (C3-62). PowerShell's
|
|
79
|
+
`ConvertFrom-Json` folds object keys case-insensitively, so two Java symbols
|
|
80
|
+
differing only by case — legal, Java is case-sensitive — collided and aborted
|
|
81
|
+
parsing of the *entire* baseline document on the default Windows consumer. `fan_in`
|
|
82
|
+
now serialises as a sorted array of `{symbol, fan_in}`, the shape `hotspots` already
|
|
83
|
+
used. Baselines captured before this release keep diffing and trending: the reader
|
|
84
|
+
accepts both shapes.
|
|
85
|
+
- **`spring-audit` no longer prints `done` before the answer exists** (C3-54, part 1).
|
|
86
|
+
The completion line was emitted inside the `finally` around the analysis, so a
|
|
87
|
+
teardown landing between it and the write left 6.8 minutes of CPU and no output
|
|
88
|
+
file behind a reported success. It now prints after the write it describes. The
|
|
89
|
+
same reorder also stops `done` from being claimed on a run whose analysis *raised*.
|
|
90
|
+
- **`ask cache model` answers in ~830 tokens instead of ~2 590** (C3-63). One line per
|
|
91
|
+
layer and per command by default; the per-command measured timings and notes move
|
|
92
|
+
behind `--full`. Every command and every layer is still named, and the invalidation
|
|
93
|
+
rule is still stated — the density fix is not a coverage cut. `--json` and
|
|
94
|
+
`--markdown` are unchanged.
|
|
95
|
+
- **The evidence manifest publishes how many claims exist, not how many fitted**
|
|
96
|
+
(invariant I-2). The claim builder returned the moment it reached its 200-claim cap,
|
|
97
|
+
so the real total was never computed and the cap was published in its place, with no
|
|
98
|
+
field carrying the truth. `claims_cap` now reads `{total, total_indexed, capped}`
|
|
99
|
+
with `total` measured before truncation. `capped` is also corrected: exactly 200
|
|
100
|
+
claims reported a cut that had not happened.
|
|
101
|
+
|
|
102
|
+
### Performance
|
|
103
|
+
|
|
104
|
+
- **The `spring-audit` rule pass is 7.8× faster on a large repository** (C3-61):
|
|
105
|
+
42.1 s → 5.4 s measured on keycloak (5 486 non-test sources, 59 144 symbols), with
|
|
106
|
+
the finding set byte-identical before and after. The cost was not spread across the
|
|
107
|
+
file population — one rule was 94 % of it, and inside it `_proxy_bypass` scanned
|
|
108
|
+
every source for every inferred gate token before establishing that the source never
|
|
109
|
+
mentions the token: 98.9 % of the work was provably empty. The guard is an
|
|
110
|
+
equivalence, not a heuristic, and no threads or processes were added, so output
|
|
111
|
+
determinism is untouched. The rule pass is no longer the dominant phase.
|
|
112
|
+
|
|
113
|
+
### Tests
|
|
114
|
+
|
|
115
|
+
- The four batteries that were red on `master` before this release are green:
|
|
116
|
+
`test_flag_normalization` compared a wall-clock timestamp between two runs;
|
|
117
|
+
`test_path_admission` had no invocation row for `audit-report`; and
|
|
118
|
+
`test_invariant_counts_from_measurement` failed on `provenance.py` (a real defect,
|
|
119
|
+
fixed above) and on `audit_report.py` (a false positive from a hand-maintained key
|
|
120
|
+
catalogue, now a rule on the key's final token — the audit's anti-vacuous guard is
|
|
121
|
+
extended rather than trusted).
|
|
122
|
+
- Every fix in this release ships a regression battery that was mutation-checked:
|
|
123
|
+
reverting the fix fails the new tests. Two of them were rewritten after the first
|
|
124
|
+
draft passed under that mutation — a summary that exists but is never reached by the
|
|
125
|
+
command, and an error path that exits before the spinner is created.
|
|
126
|
+
|
|
5
127
|
## [4.10.3] — 2026-08-05
|
|
6
128
|
|
|
7
129
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.10.
|
|
3
|
+
Version: 4.10.5
|
|
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.5 — 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.5 — 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.5"
|
|
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,9 +15,10 @@ 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
|
+
from sourcecode.progress import output_emitted as _progress_output_emitted
|
|
21
22
|
from sourcecode import perf
|
|
22
23
|
from sourcecode.caller_metrics import (
|
|
23
24
|
CALLER_METRIC_RECONCILIATION,
|
|
@@ -888,8 +889,27 @@ def _safe_write_file(path: "Path", content: str) -> None:
|
|
|
888
889
|
path = Path(path)
|
|
889
890
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
890
891
|
tmp = path.with_name(f".{path.name}.tmp-{os.getpid()}-{time.time_ns()}")
|
|
891
|
-
|
|
892
|
+
# C3-54 part 2: the rename is atomic, the bytes were not durable. A
|
|
893
|
+
# process torn down between `write_text` and the OS flushing its page
|
|
894
|
+
# cache leaves a file that exists at full length and holds nothing —
|
|
895
|
+
# the same total loss the false `done` line reported as success, one
|
|
896
|
+
# layer down. fsync the data before the rename, then the directory
|
|
897
|
+
# entry, so a run that reports an output file has one.
|
|
898
|
+
with open(tmp, "w", encoding="utf-8") as fh:
|
|
899
|
+
fh.write(content)
|
|
900
|
+
fh.flush()
|
|
901
|
+
os.fsync(fh.fileno())
|
|
892
902
|
tmp.replace(path)
|
|
903
|
+
try:
|
|
904
|
+
dir_fd = os.open(str(path.parent), os.O_RDONLY)
|
|
905
|
+
try:
|
|
906
|
+
os.fsync(dir_fd)
|
|
907
|
+
finally:
|
|
908
|
+
os.close(dir_fd)
|
|
909
|
+
except (OSError, AttributeError):
|
|
910
|
+
# Directory fsync is POSIX-only; on Windows the rename is already
|
|
911
|
+
# ordered against the data write. Never fail a good run over it.
|
|
912
|
+
pass
|
|
893
913
|
try:
|
|
894
914
|
from sourcecode.runs import active_run
|
|
895
915
|
run = active_run()
|
|
@@ -1213,6 +1233,9 @@ def _emit_command_output(
|
|
|
1213
1233
|
# The payload is on stdout here, so this cannot join it; it is a
|
|
1214
1234
|
# notice and takes the terminal gate rather than dirtying a capture.
|
|
1215
1235
|
_notice("✓ copied to clipboard")
|
|
1236
|
+
# The answer has been handed over. Any `done (Xs)` a command deferred is now
|
|
1237
|
+
# a true claim and prints here — never before this line (C3-54).
|
|
1238
|
+
_progress_output_emitted()
|
|
1216
1239
|
|
|
1217
1240
|
|
|
1218
1241
|
def _emit_text_output(text: str, copy: bool) -> None:
|
|
@@ -1228,6 +1251,7 @@ def _emit_text_output(text: str, copy: bool) -> None:
|
|
|
1228
1251
|
typer.echo(text)
|
|
1229
1252
|
if copy and _copy_to_clipboard(text):
|
|
1230
1253
|
_notice("✓ copied to clipboard")
|
|
1254
|
+
_progress_output_emitted()
|
|
1231
1255
|
|
|
1232
1256
|
|
|
1233
1257
|
class _TextAnswer:
|
|
@@ -1257,6 +1281,7 @@ class _TextAnswer:
|
|
|
1257
1281
|
def done(self, copy: bool) -> None:
|
|
1258
1282
|
if copy and self._lines and _copy_to_clipboard(self.text):
|
|
1259
1283
|
_notice("✓ copied to clipboard")
|
|
1284
|
+
_progress_output_emitted()
|
|
1260
1285
|
|
|
1261
1286
|
|
|
1262
1287
|
# H-06: Intercept Click-level UsageError (unknown options, bad args) and emit JSON.
|
|
@@ -4825,8 +4850,15 @@ def repo_ir_cmd(
|
|
|
4825
4850
|
size_bytes = len(raw_bytes)
|
|
4826
4851
|
if gzip_output:
|
|
4827
4852
|
import gzip as _gzip
|
|
4853
|
+
# Same durability rule as `_safe_write_file`: the bytes are on disk
|
|
4854
|
+
# before anything says they are (C3-54).
|
|
4828
4855
|
with _gzip.open(output_path, "wb") as _gz:
|
|
4829
4856
|
_gz.write(raw_bytes)
|
|
4857
|
+
_gz.flush()
|
|
4858
|
+
try:
|
|
4859
|
+
os.fsync(_gz.fileno())
|
|
4860
|
+
except (OSError, AttributeError):
|
|
4861
|
+
pass
|
|
4830
4862
|
compressed_kb = output_path.stat().st_size // 1024
|
|
4831
4863
|
size_kb = size_bytes // 1024
|
|
4832
4864
|
# C3-26: when the payload went to a file, this confirmation IS the
|
|
@@ -4837,7 +4869,9 @@ def repo_ir_cmd(
|
|
|
4837
4869
|
f"IR written to {output_path} ({compressed_kb}KB gzip, {size_kb}KB uncompressed)"
|
|
4838
4870
|
)
|
|
4839
4871
|
else:
|
|
4840
|
-
|
|
4872
|
+
# Was a bare `write_bytes`: neither atomic nor durable, and the only
|
|
4873
|
+
# `-o` write in the CLI that skipped the shared seam (C3-54).
|
|
4874
|
+
_safe_write_file(output_path, output)
|
|
4841
4875
|
size_kb = size_bytes // 1024
|
|
4842
4876
|
if summary_only:
|
|
4843
4877
|
typer.echo(
|
|
@@ -4850,6 +4884,7 @@ def repo_ir_cmd(
|
|
|
4850
4884
|
f"IR written to {output_path} "
|
|
4851
4885
|
f"({size_kb}KB, {n_nodes} nodes, {n_edges} edges)"
|
|
4852
4886
|
)
|
|
4887
|
+
_progress_output_emitted()
|
|
4853
4888
|
else:
|
|
4854
4889
|
if gzip_output:
|
|
4855
4890
|
_emit_error_json(
|
|
@@ -7402,7 +7437,14 @@ def spring_audit_cmd(
|
|
|
7402
7437
|
except Exception:
|
|
7403
7438
|
pass
|
|
7404
7439
|
finally:
|
|
7405
|
-
|
|
7440
|
+
# C3-54: `done` must not claim the answer exists before it does. The
|
|
7441
|
+
# field log showed `[ask] done (6.8m)` printed, then the process torn
|
|
7442
|
+
# down before `-o` was written — a false success on 6.8 minutes of
|
|
7443
|
+
# CPU. `stop()` here only clears the spinner (satisfies the standing
|
|
7444
|
+
# rule that a raised error must never print under a live spinner
|
|
7445
|
+
# frame); it prints no completion line. `_prog.finish()` runs below,
|
|
7446
|
+
# after the render AND the write have actually happened.
|
|
7447
|
+
_prog.stop()
|
|
7406
7448
|
if table:
|
|
7407
7449
|
output = _render_spring_audit_table(
|
|
7408
7450
|
combined, rule_filter=rule_filter, top_n=top_n, min_severity=min_severity
|
|
@@ -7415,6 +7457,7 @@ def spring_audit_cmd(
|
|
|
7415
7457
|
_total = combined.summary.get("total_findings", 0)
|
|
7416
7458
|
_emit_command_output(output, output_path, copy,
|
|
7417
7459
|
success_msg=f"Spring audit written to {output_path} ({_total} findings)")
|
|
7460
|
+
_prog.finish()
|
|
7418
7461
|
|
|
7419
7462
|
if ci and combined.findings:
|
|
7420
7463
|
raise typer.Exit(code=1)
|
|
@@ -9453,18 +9496,21 @@ def pr_impact_cmd(
|
|
|
9453
9496
|
# --files accepts three shapes: a list file, '-' for stdin, or an inline
|
|
9454
9497
|
# comma-separated list. The list-file shape was the only documented one and
|
|
9455
9498
|
# the only one the error hint mentioned.
|
|
9456
|
-
|
|
9499
|
+
# Both byte-carrying shapes decode through `text_input`: stdin and list files
|
|
9500
|
+
# arrive as somebody else's shell wrote them, and the locale codepage is not
|
|
9501
|
+
# evidence of their encoding (C3-58).
|
|
9502
|
+
from sourcecode import text_input as _text_input
|
|
9503
|
+
|
|
9504
|
+
_files_raw = str(files).replace(_text_input.BOM_CHAR, "").strip()
|
|
9457
9505
|
if _files_raw == "-":
|
|
9458
|
-
changed_files =
|
|
9506
|
+
changed_files = _text_input.parse_path_lines(_text_input.read_stdin_text())
|
|
9459
9507
|
_files_desc = "stdin"
|
|
9460
9508
|
else:
|
|
9461
9509
|
_files_path = Path(_files_raw)
|
|
9462
9510
|
if _files_path.is_file():
|
|
9463
|
-
changed_files =
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
if line.strip()
|
|
9467
|
-
]
|
|
9511
|
+
changed_files = _text_input.parse_path_lines(
|
|
9512
|
+
_text_input.read_text_file(_files_path)
|
|
9513
|
+
)
|
|
9468
9514
|
_files_desc = str(_files_path)
|
|
9469
9515
|
elif all(
|
|
9470
9516
|
p.strip().lower().endswith(".java")
|
|
@@ -10301,6 +10347,9 @@ def modernize_cmd(
|
|
|
10301
10347
|
sys.stdout.buffer.flush()
|
|
10302
10348
|
except AttributeError:
|
|
10303
10349
|
sys.stdout.write(output + "\n")
|
|
10350
|
+
# This branch predates `_emit_command_output`, so it also has to release the
|
|
10351
|
+
# deferred completion line itself (C3-54).
|
|
10352
|
+
_progress_output_emitted()
|
|
10304
10353
|
|
|
10305
10354
|
if copy:
|
|
10306
10355
|
_copy_to_clipboard(output)
|
|
@@ -11569,12 +11618,33 @@ def mcp_status(copy: bool = _copy_option()) -> None:
|
|
|
11569
11618
|
_answer.say("")
|
|
11570
11619
|
|
|
11571
11620
|
# Stage 1: Dependencies
|
|
11621
|
+
# C3-57: check the symbol this CLI actually consumes (FastMCP), not just
|
|
11622
|
+
# whether the root `mcp` package imports — `mcp>=2.0` imports cleanly but
|
|
11623
|
+
# has no `mcp.server.fastmcp` module, so importing only the root package
|
|
11624
|
+
# reports a green check for a build that will fail on `mcp serve`/`mcp list-tools`.
|
|
11625
|
+
def _installed_mcp_version() -> str:
|
|
11626
|
+
# The `mcp` package does not set `__version__`; read installed
|
|
11627
|
+
# distribution metadata instead, and fall back gracefully off a
|
|
11628
|
+
# normal install (editable installs, vendored copies, etc.).
|
|
11629
|
+
try:
|
|
11630
|
+
from importlib.metadata import version as _dist_version
|
|
11631
|
+
return _dist_version("mcp")
|
|
11632
|
+
except Exception:
|
|
11633
|
+
return "unknown"
|
|
11634
|
+
|
|
11572
11635
|
try:
|
|
11573
|
-
|
|
11574
|
-
|
|
11636
|
+
from mcp.server.fastmcp import FastMCP # noqa: F401
|
|
11637
|
+
_mcp_ver = _installed_mcp_version()
|
|
11638
|
+
_answer.say(f"Dependencies ✓ installed (mcp {_mcp_ver})")
|
|
11575
11639
|
except ImportError:
|
|
11576
|
-
|
|
11577
|
-
|
|
11640
|
+
try:
|
|
11641
|
+
import mcp as _mcp_pkg # noqa: F401
|
|
11642
|
+
_mcp_ver = _installed_mcp_version()
|
|
11643
|
+
_answer.say(f"Dependencies ✗ mcp {_mcp_ver} incompatible (requires mcp>=1.2,<2)")
|
|
11644
|
+
_answer.say(" Fix: pip install 'sourcecode[mcp]' --upgrade")
|
|
11645
|
+
except ImportError:
|
|
11646
|
+
_answer.say("Dependencies ✗ missing")
|
|
11647
|
+
_answer.say(" Fix: pip install sourcecode[mcp]")
|
|
11578
11648
|
_answer.say("")
|
|
11579
11649
|
|
|
11580
11650
|
clients = detect_clients()
|
|
@@ -11789,9 +11859,32 @@ def mcp_list_tools(
|
|
|
11789
11859
|
import asyncio
|
|
11790
11860
|
import json as _json
|
|
11791
11861
|
|
|
11792
|
-
|
|
11862
|
+
# C3-57: this command previously let a raw Python traceback through when
|
|
11863
|
+
# the installed `mcp` package was incompatible (e.g. mcp>=2.0, which has
|
|
11864
|
+
# no mcp.server.fastmcp) — the only command in the catalogue to do so.
|
|
11865
|
+
# Route failures through the same structured error contract as every
|
|
11866
|
+
# other command instead.
|
|
11867
|
+
try:
|
|
11868
|
+
from sourcecode.mcp.server import mcp as _mcp
|
|
11869
|
+
tools = asyncio.run(_mcp.list_tools())
|
|
11870
|
+
except ImportError as exc:
|
|
11871
|
+
_emit_error_json(
|
|
11872
|
+
EXECUTION_FAILED_CODE,
|
|
11873
|
+
f"MCP server dependencies are not importable: {exc}",
|
|
11874
|
+
hint="Run 'ask mcp status' to check the installed mcp package version — "
|
|
11875
|
+
"this CLI requires mcp>=1.2,<2. Fix: pip install 'sourcecode[mcp]' --upgrade",
|
|
11876
|
+
expected="mcp>=1.2,<2 installed with mcp.server.fastmcp.FastMCP importable.",
|
|
11877
|
+
)
|
|
11878
|
+
raise typer.Exit(code=1)
|
|
11879
|
+
except Exception as exc:
|
|
11880
|
+
_emit_error_json(
|
|
11881
|
+
EXECUTION_FAILED_CODE,
|
|
11882
|
+
f"Failed to list MCP tools: {type(exc).__name__}: {exc}",
|
|
11883
|
+
hint="Run 'ask mcp status' to check MCP server health.",
|
|
11884
|
+
expected="A list of registered MCP tools.",
|
|
11885
|
+
)
|
|
11886
|
+
raise typer.Exit(code=1)
|
|
11793
11887
|
|
|
11794
|
-
tools = asyncio.run(_mcp.list_tools())
|
|
11795
11888
|
tools_sorted = sorted(tools, key=lambda t: t.name)
|
|
11796
11889
|
|
|
11797
11890
|
if json_output:
|
|
@@ -12057,6 +12150,11 @@ def cache_model_cmd(
|
|
|
12057
12150
|
path: Path = typer.Argument(Path("."), help="Repository path for CLI consistency (model is global)."),
|
|
12058
12151
|
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
12059
12152
|
markdown: bool = typer.Option(False, "--markdown", help="Output the tables published in the user guide."),
|
|
12153
|
+
full: bool = typer.Option(
|
|
12154
|
+
False, "--full",
|
|
12155
|
+
help="Full prose: every command's measured timing and note (~2.5k tokens). "
|
|
12156
|
+
"Default is a compact one-line-per-command summary.",
|
|
12157
|
+
),
|
|
12060
12158
|
copy: bool = _copy_option(),
|
|
12061
12159
|
) -> None:
|
|
12062
12160
|
"""What each cache layer stores, what invalidates it, and what a warm helps.
|
|
@@ -12070,6 +12168,10 @@ def cache_model_cmd(
|
|
|
12070
12168
|
|
|
12071
12169
|
One rule covers invalidation: every layer keys on the exact tree state, so any
|
|
12072
12170
|
change to the analysed files invalidates it, committed or not.
|
|
12171
|
+
|
|
12172
|
+
\b
|
|
12173
|
+
Default output is a compact summary (C3-63) — every command named, without the
|
|
12174
|
+
per-command measured timing and note. Pass --full for that detail.
|
|
12073
12175
|
"""
|
|
12074
12176
|
from sourcecode import cache_model as _cmodel
|
|
12075
12177
|
|
|
@@ -12089,7 +12191,8 @@ def cache_model_cmd(
|
|
|
12089
12191
|
f"Scope: {Path(path).resolve()} (model is global; path accepted for "
|
|
12090
12192
|
"cache command grammar consistency)\n\n"
|
|
12091
12193
|
)
|
|
12092
|
-
|
|
12194
|
+
_body = _cmodel.render_text() if full else _cmodel.render_summary()
|
|
12195
|
+
_emit_text_output(_prefix + _body, copy)
|
|
12093
12196
|
|
|
12094
12197
|
|
|
12095
12198
|
@cache_app.command("freshness", short_help="RIS freshness relative to the current git HEAD")
|