sourcecode 4.9.1__tar.gz → 4.10.1__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.9.1 → sourcecode-4.10.1}/CHANGELOG.md +50 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/PKG-INFO +9 -7
- {sourcecode-4.9.1 → sourcecode-4.10.1}/README.md +8 -6
- {sourcecode-4.9.1 → sourcecode-4.10.1}/pyproject.toml +1 -1
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/audit_report.py +10 -2
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/cache_model.py +6 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/canonical_ir.py +19 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/cli.py +114 -4
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/migrate_check.py +1 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/progress.py +21 -6
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/provenance.py +12 -9
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/release_info.py +1 -1
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/repository_ir.py +21 -0
- sourcecode-4.10.1/src/sourcecode/runs.py +136 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/security_config_scan.py +55 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/.gitignore +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/.ruff.toml +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/CLAUDE.md +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/CONTRIBUTING.md +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/LICENSE +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/SECURITY.md +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/raw +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/scripts/perf_harness.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/license.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/source_text.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/functions/README.md +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.9.1 → sourcecode-4.10.1}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.10.1] — 2026-08-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`cache model` now includes `audit-report`.** Warm-cache documentation and
|
|
10
|
+
machine output now agree that `audit-report` reuses the same structural layers
|
|
11
|
+
as the other deep analysis/reporting commands.
|
|
12
|
+
- **`migrate-check --blast-radius` no longer changes unrelated provenance.**
|
|
13
|
+
The optional blast-radius flag now adds only the blast-radius block; the shared
|
|
14
|
+
evidence manifest stays deterministic for shape-comparison consumers.
|
|
15
|
+
- **`impact` expands class/interface seeds to in-repository subtypes.** A base
|
|
16
|
+
class or interface query now reaches callers of subtype members, matching the
|
|
17
|
+
endpoint-reach behavior already expected from `impact-chain`.
|
|
18
|
+
- **Severity fields are locked to named units.** Tests now preserve the contract
|
|
19
|
+
that declared scanner severity, composed effective severity and risk band are
|
|
20
|
+
separate fields rather than contradictory values for the same claim.
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
- README, user guide, roadmap and defect ledger now reflect the 4.10.1 patch
|
|
25
|
+
status and close stale rows that were already fixed by measured tests.
|
|
26
|
+
|
|
27
|
+
## [4.10.0] — 2026-08-05
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Canonical endpoint projection rejects parser-corrupt Java handler names.**
|
|
32
|
+
Reserved Java keywords such as `if` can no longer reach CIR/risk as synthetic
|
|
33
|
+
handler symbols.
|
|
34
|
+
- **`DEAD-001` reports commented Java security annotations.** Java comments that
|
|
35
|
+
contain Spring/Jakarta/custom security annotations are now treated as disabled
|
|
36
|
+
controls, while active annotations stay out of dead-code findings.
|
|
37
|
+
- **`SEC-007` reports JavaMelody descriptor credentials.** `authorized-users`
|
|
38
|
+
values in deployment descriptors are detected and redacted even when they are
|
|
39
|
+
not written as `{SHA-*}` digest literals.
|
|
40
|
+
- **`audit-report` can reuse existing risk evidence.** `--from-risk risk.json`
|
|
41
|
+
packages a previous `ask risk` payload instead of recomputing repo-wide risk.
|
|
42
|
+
- **Long analysis observability improved.** `ASK_PROGRESS=1` enables non-TTY
|
|
43
|
+
heartbeat lines, long commands write durable `.ask/runs` records, and file
|
|
44
|
+
outputs are written atomically.
|
|
45
|
+
- **Large-repo CI budgets are explicit.** `ASK_MAX_ANALYSIS_SECONDS` can fail a
|
|
46
|
+
repo-wide/deep command before work starts when the configured budget is below
|
|
47
|
+
the command's safe floor.
|
|
48
|
+
|
|
49
|
+
### Documentation
|
|
50
|
+
|
|
51
|
+
- README, user guide and root `ask --help` now distinguish per-symbol,
|
|
52
|
+
inventory and repo-wide/deep command costs instead of treating cache
|
|
53
|
+
benchmarks as universal runtime promises.
|
|
54
|
+
|
|
5
55
|
## [4.9.1] — 2026-08-04
|
|
6
56
|
|
|
7
57
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.10.1
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -54,9 +54,9 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
|
|
55
55
|
## The problem
|
|
56
56
|
|
|
57
|
-
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch.
|
|
57
|
+
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. On small and mid-sized repos that cost is seconds; on large Java/Spring monoliths, repo-wide semantic composition can be minutes. Multiply that by dozens of agent turns per hour, and repo context acquisition becomes a real bottleneck — not just latency, but tokens, compute, and iteration velocity.
|
|
58
58
|
|
|
59
|
-
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan,
|
|
59
|
+
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, later invocations reuse pre-built context whenever their command can consume the warmed layers. The repo doesn't change? The cache doesn't expire.
|
|
60
60
|
|
|
61
61
|
**The cache is not a performance optimization. It is what makes ASK Engine usable as infrastructure rather than a one-off tool.**
|
|
62
62
|
|
|
@@ -69,9 +69,11 @@ ASK Engine solves this with a persistent structural cache keyed on file content
|
|
|
69
69
|
| Keycloak | 7,885 Java files | 10.5s | 0.6s | **~17x** |
|
|
70
70
|
| BroadleafCommerce | 2,985 Java files | 2.7s | 0.3s | **~9x** |
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
These numbers are cache-layer benchmarks, not a promise for every command. Per-symbol queries and compact context benefit most. Inventory commands scale with file count. Repo-wide/deep compositions such as `risk`, `audit-report` and large `spring-audit` runs can take minutes on multi-thousand-endpoint repositories and belong in a supervised or nightly job unless CI sets an explicit budget.
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
With the right command class, ASK Engine becomes **constant infrastructure** inside agent loops — call the fast/context surfaces before edits and reserve deep compositions for the points where their evidence is worth the runtime.
|
|
75
|
+
|
|
76
|
+
**Large-repo controls.** Set `ASK_PROGRESS=1` to get line heartbeats in non-TTY supervised runs. Set `ASK_MAX_ANALYSIS_SECONDS=<seconds>` to fail before starting a repo-wide/deep command whose configured budget is below its safe floor. Use `ask audit-report --from-risk risk.json` to package an existing risk run instead of recomputing it.
|
|
75
77
|
|
|
76
78
|
**What a warm actually covers.** `ask cache warm` runs the compact analysis: it rebuilds the
|
|
77
79
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
@@ -97,7 +99,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
97
99
|
# pip / pipx
|
|
98
100
|
pipx install sourcecode # or: pip install sourcecode
|
|
99
101
|
|
|
100
|
-
ask version # ask 4.
|
|
102
|
+
ask version # ask 4.10.1 — and, on a build that has aged,
|
|
101
103
|
# how many releases have probably shipped since
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -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`**
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
## The problem
|
|
18
18
|
|
|
19
|
-
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch.
|
|
19
|
+
Every time an AI coding agent starts a new session, it has to re-parse the repository from scratch. On small and mid-sized repos that cost is seconds; on large Java/Spring monoliths, repo-wide semantic composition can be minutes. Multiply that by dozens of agent turns per hour, and repo context acquisition becomes a real bottleneck — not just latency, but tokens, compute, and iteration velocity.
|
|
20
20
|
|
|
21
|
-
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan,
|
|
21
|
+
ASK Engine solves this with a persistent structural cache keyed on file content hashes. After the first scan, later invocations reuse pre-built context whenever their command can consume the warmed layers. The repo doesn't change? The cache doesn't expire.
|
|
22
22
|
|
|
23
23
|
**The cache is not a performance optimization. It is what makes ASK Engine usable as infrastructure rather than a one-off tool.**
|
|
24
24
|
|
|
@@ -31,9 +31,11 @@ ASK Engine solves this with a persistent structural cache keyed on file content
|
|
|
31
31
|
| Keycloak | 7,885 Java files | 10.5s | 0.6s | **~17x** |
|
|
32
32
|
| BroadleafCommerce | 2,985 Java files | 2.7s | 0.3s | **~9x** |
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
These numbers are cache-layer benchmarks, not a promise for every command. Per-symbol queries and compact context benefit most. Inventory commands scale with file count. Repo-wide/deep compositions such as `risk`, `audit-report` and large `spring-audit` runs can take minutes on multi-thousand-endpoint repositories and belong in a supervised or nightly job unless CI sets an explicit budget.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
With the right command class, ASK Engine becomes **constant infrastructure** inside agent loops — call the fast/context surfaces before edits and reserve deep compositions for the points where their evidence is worth the runtime.
|
|
37
|
+
|
|
38
|
+
**Large-repo controls.** Set `ASK_PROGRESS=1` to get line heartbeats in non-TTY supervised runs. Set `ASK_MAX_ANALYSIS_SECONDS=<seconds>` to fail before starting a repo-wide/deep command whose configured budget is below its safe floor. Use `ask audit-report --from-risk risk.json` to package an existing risk run instead of recomputing it.
|
|
37
39
|
|
|
38
40
|
**What a warm actually covers.** `ask cache warm` runs the compact analysis: it rebuilds the
|
|
39
41
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
@@ -59,7 +61,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
59
61
|
# pip / pipx
|
|
60
62
|
pipx install sourcecode # or: pip install sourcecode
|
|
61
63
|
|
|
62
|
-
ask version # ask 4.
|
|
64
|
+
ask version # ask 4.10.1 — and, on a build that has aged,
|
|
63
65
|
# how many releases have probably shipped since
|
|
64
66
|
```
|
|
65
67
|
|
|
@@ -39,13 +39,19 @@ def build_audit_report(
|
|
|
39
39
|
profiles: Optional[set[str]] = None,
|
|
40
40
|
sign_key: Optional[bytes] = None,
|
|
41
41
|
risk_limit: int = 10,
|
|
42
|
+
risk_payload: Optional[dict[str, Any]] = None,
|
|
42
43
|
) -> dict[str, Any]:
|
|
43
44
|
"""Build the buyer-readable audit bundle from existing command payloads."""
|
|
44
45
|
from sourcecode.posture import build_posture
|
|
45
|
-
from sourcecode.risk import build_risk
|
|
46
46
|
|
|
47
47
|
root = Path(root).resolve()
|
|
48
|
-
|
|
48
|
+
if risk_payload is None:
|
|
49
|
+
from sourcecode.risk import build_risk
|
|
50
|
+
risk = build_risk(root, limit=risk_limit, min_band="low", profiles=profiles)
|
|
51
|
+
risk_source = "computed"
|
|
52
|
+
else:
|
|
53
|
+
risk = risk_payload
|
|
54
|
+
risk_source = "from-risk"
|
|
49
55
|
posture = build_posture(root, profiles or set())
|
|
50
56
|
top_risks = risk.get("risks", [])[:risk_limit]
|
|
51
57
|
access = ((posture.get("endpoints") or {}).get("effective_access") or {})
|
|
@@ -58,6 +64,7 @@ def build_audit_report(
|
|
|
58
64
|
**({"profile_set": sorted(profiles)} if profiles else {}),
|
|
59
65
|
"summary": {
|
|
60
66
|
"risk_model": risk.get("model"),
|
|
67
|
+
"risk_source": risk_source,
|
|
61
68
|
"total_defects": risk.get("total_defects"),
|
|
62
69
|
"total_findings": risk.get("total_findings"),
|
|
63
70
|
"risk_bands": risk.get("by_band", {}),
|
|
@@ -86,6 +93,7 @@ def build_audit_report(
|
|
|
86
93
|
inputs={
|
|
87
94
|
"profiles": sorted(profiles) if profiles else None,
|
|
88
95
|
"risk_limit": risk_limit,
|
|
96
|
+
"risk_source": risk_source,
|
|
89
97
|
"signed": bool(sign_key),
|
|
90
98
|
},
|
|
91
99
|
)
|
|
@@ -144,6 +144,12 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
144
144
|
"repository side, so what it buys is what it buys `risk`.",
|
|
145
145
|
"not measured on the battery yet — bounded by the `risk` composition, plus "
|
|
146
146
|
"reading one JSON file"),
|
|
147
|
+
CommandCache("audit-report", ("cir", "parse"), "shared", False,
|
|
148
|
+
"Packages `risk` and `posture` evidence. A warm helps the repository side; "
|
|
149
|
+
"`--from-risk risk.json` skips risk recomputation entirely and only builds "
|
|
150
|
+
"the posture/report projection.",
|
|
151
|
+
"not measured on the battery yet — bounded by `risk` + `posture`, or by "
|
|
152
|
+
"`posture` when `--from-risk` is supplied"),
|
|
147
153
|
CommandCache("migrate-recipe", ("parse",), "shared", False,
|
|
148
154
|
"Runs the same scan as `migrate-check` and projects its findings into an "
|
|
149
155
|
"OpenRewrite recipe, so it buys exactly what a warm buys `migrate-check`: "
|
|
@@ -43,6 +43,23 @@ _REVERSE_EXCLUDE: frozenset[str] = frozenset({"annotated_with", "mapped_to"})
|
|
|
43
43
|
# the CIR endpoint count reconciles with the `endpoints` command). See BUG #7.
|
|
44
44
|
_FQN_PATH_RE: re.Pattern = re.compile(r"/(org|com|net|io|edu)\.[a-z][a-z0-9]*\.[a-zA-Z]")
|
|
45
45
|
|
|
46
|
+
_JAVA_RESERVED_HANDLER_NAMES: frozenset[str] = frozenset({
|
|
47
|
+
"if", "else", "for", "while", "do", "switch", "case", "break", "continue",
|
|
48
|
+
"return", "new", "throw", "try", "catch", "finally", "instanceof",
|
|
49
|
+
"this", "super", "void", "class", "interface", "enum", "extends", "implements",
|
|
50
|
+
"import", "package", "static", "final", "abstract", "synchronized", "native",
|
|
51
|
+
"true", "false", "null",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _route_has_reserved_handler(route: dict) -> bool:
|
|
56
|
+
"""Reject parser-corrupt endpoint symbols such as ``pkg.Controller#if``."""
|
|
57
|
+
symbol = str(route.get("symbol") or "")
|
|
58
|
+
if "#" not in symbol:
|
|
59
|
+
return False
|
|
60
|
+
handler = symbol.rsplit("#", 1)[1].split("(", 1)[0].strip()
|
|
61
|
+
return handler in _JAVA_RESERVED_HANDLER_NAMES
|
|
62
|
+
|
|
46
63
|
|
|
47
64
|
# ---------------------------------------------------------------------------
|
|
48
65
|
# CanonicalSecurity
|
|
@@ -438,6 +455,8 @@ def ir_dict_to_canonical(
|
|
|
438
455
|
for r in route_surface:
|
|
439
456
|
if r.get("scope") == "test_util":
|
|
440
457
|
continue
|
|
458
|
+
if _route_has_reserved_handler(r):
|
|
459
|
+
continue
|
|
441
460
|
if _FQN_PATH_RE.search(r.get("path", "") or ""):
|
|
442
461
|
continue
|
|
443
462
|
ep = _route_to_canonical_endpoint(r)
|
|
@@ -338,8 +338,10 @@ def _build_help_text() -> str:
|
|
|
338
338
|
Deterministic Java/Spring semantics and reusable structural context for AI coding agents.
|
|
339
339
|
|
|
340
340
|
Cache warms on first scan; later calls reuse pre-built context instead of rescanning.
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
Performance depends on command class and repo size: per-symbol queries are the fast path,
|
|
342
|
+
inventory commands scale with files, and repo-wide/deep compositions can take
|
|
343
|
+
minutes on multi-thousand-endpoint repositories. Use deep jobs nightly or with
|
|
344
|
+
ASK_MAX_ANALYSIS_SECONDS/ASK_PROGRESS when CI needs an explicit budget.
|
|
343
345
|
|
|
344
346
|
[bold]Start here — Java/Spring analysis:[/bold]
|
|
345
347
|
posture . --diff dev:prod [dim]# effective access, two profile sets (exp.)[/dim]
|
|
@@ -881,9 +883,24 @@ def _enforce_format(command: str, fmt: str) -> None:
|
|
|
881
883
|
|
|
882
884
|
|
|
883
885
|
def _safe_write_file(path: "Path", content: str) -> None:
|
|
884
|
-
"""
|
|
886
|
+
"""Atomically write content to path, emitting a clean JSON error on I/O failure."""
|
|
885
887
|
try:
|
|
886
|
-
path
|
|
888
|
+
path = Path(path)
|
|
889
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
890
|
+
tmp = path.with_name(f".{path.name}.tmp-{os.getpid()}-{time.time_ns()}")
|
|
891
|
+
tmp.write_text(content, encoding="utf-8")
|
|
892
|
+
tmp.replace(path)
|
|
893
|
+
try:
|
|
894
|
+
from sourcecode.runs import active_run
|
|
895
|
+
run = active_run()
|
|
896
|
+
if run is not None:
|
|
897
|
+
run.write(
|
|
898
|
+
status="running",
|
|
899
|
+
output_path=path,
|
|
900
|
+
output_size=path.stat().st_size,
|
|
901
|
+
)
|
|
902
|
+
except Exception:
|
|
903
|
+
pass
|
|
887
904
|
except OSError as _exc:
|
|
888
905
|
_emit_error_json(
|
|
889
906
|
INVALID_INPUT_CODE,
|
|
@@ -968,6 +985,50 @@ def _active_command_context() -> "tuple[str, Optional[Path]]":
|
|
|
968
985
|
|
|
969
986
|
|
|
970
987
|
_EXPENSIVE_ANALYSIS_TTL_SECONDS = 6 * 60 * 60
|
|
988
|
+
_ANALYSIS_CLASSES: dict[str, tuple[str, int, str]] = {
|
|
989
|
+
"spring-audit": ("repo-wide", 600, "use nightly/deep tier on large repositories"),
|
|
990
|
+
"risk": ("deep", 1200, "run `ask spring-audit` or a saved `risk.json` first"),
|
|
991
|
+
"audit-report": ("deep", 1200, "reuse `--from-risk risk.json` or schedule nightly"),
|
|
992
|
+
"verify --init": ("repo-wide", 600, "derive contracts outside pre-commit"),
|
|
993
|
+
"verify": ("core", 60, "run with a larger CI budget or narrow the contract set"),
|
|
994
|
+
"migrate-check": ("repo-wide", 600, "run inventory in CI and deep migration checks nightly"),
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def _analysis_budget(command: str) -> dict[str, Any]:
|
|
999
|
+
cls, recommended, fallback = _ANALYSIS_CLASSES.get(
|
|
1000
|
+
command, ("repo-wide", 600, "run this command in a supervised or nightly job")
|
|
1001
|
+
)
|
|
1002
|
+
configured: Optional[float] = None
|
|
1003
|
+
raw = os.environ.get("ASK_MAX_ANALYSIS_SECONDS")
|
|
1004
|
+
if raw:
|
|
1005
|
+
try:
|
|
1006
|
+
configured = float(raw)
|
|
1007
|
+
except ValueError:
|
|
1008
|
+
configured = None
|
|
1009
|
+
return {
|
|
1010
|
+
"class": cls,
|
|
1011
|
+
"recommended_min_seconds": recommended,
|
|
1012
|
+
"configured_max_seconds": configured,
|
|
1013
|
+
"fallback": fallback,
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def _enforce_analysis_budget(command: str, budget: dict[str, Any]) -> None:
|
|
1018
|
+
configured = budget.get("configured_max_seconds")
|
|
1019
|
+
recommended = float(budget.get("recommended_min_seconds") or 0)
|
|
1020
|
+
if configured is None or float(configured) >= recommended:
|
|
1021
|
+
return
|
|
1022
|
+
_emit_error_json(
|
|
1023
|
+
INVALID_INPUT_CODE,
|
|
1024
|
+
(
|
|
1025
|
+
f"{command} is a {budget.get('class')} analysis and the configured "
|
|
1026
|
+
f"budget ({configured:g}s) is below its safe floor ({recommended:g}s)."
|
|
1027
|
+
),
|
|
1028
|
+
hint=str(budget.get("fallback") or "raise ASK_MAX_ANALYSIS_SECONDS"),
|
|
1029
|
+
expected=f"ASK_MAX_ANALYSIS_SECONDS >= {recommended:g}",
|
|
1030
|
+
)
|
|
1031
|
+
raise typer.Exit(code=2)
|
|
971
1032
|
|
|
972
1033
|
|
|
973
1034
|
def _analysis_lock_path(path: Path) -> Path:
|
|
@@ -1051,8 +1112,14 @@ def _emit_analysis_contention_warning(command: str, path: Path, phase: str) -> N
|
|
|
1051
1112
|
@contextlib.contextmanager
|
|
1052
1113
|
def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
1053
1114
|
"""Register a long repository-wide analysis and warn on concurrent runs."""
|
|
1115
|
+
from sourcecode.runs import RunRecord, reset_active_run, set_active_run
|
|
1116
|
+
|
|
1054
1117
|
lock_path = _analysis_lock_path(path)
|
|
1055
1118
|
_emit_analysis_contention_warning(command, path, phase)
|
|
1119
|
+
budget = _analysis_budget(command)
|
|
1120
|
+
_enforce_analysis_budget(command, budget)
|
|
1121
|
+
run_record = RunRecord.start(path, command, phase, budget=budget)
|
|
1122
|
+
run_token = set_active_run(run_record)
|
|
1056
1123
|
token = f"{os.getpid()}:{time.time_ns()}"
|
|
1057
1124
|
owns_lock = False
|
|
1058
1125
|
if _active_analysis_record(lock_path) is None:
|
|
@@ -1077,6 +1144,12 @@ def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
|
1077
1144
|
owns_lock = False
|
|
1078
1145
|
try:
|
|
1079
1146
|
yield
|
|
1147
|
+
if run_record is not None:
|
|
1148
|
+
run_record.write(status="complete", exit_code=0)
|
|
1149
|
+
except BaseException as exc:
|
|
1150
|
+
if run_record is not None:
|
|
1151
|
+
run_record.write(status="failed", exit_code=1, error=str(exc))
|
|
1152
|
+
raise
|
|
1080
1153
|
finally:
|
|
1081
1154
|
if owns_lock:
|
|
1082
1155
|
try:
|
|
@@ -1088,6 +1161,7 @@ def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
|
1088
1161
|
lock_path.unlink()
|
|
1089
1162
|
except OSError:
|
|
1090
1163
|
pass
|
|
1164
|
+
reset_active_run(run_token)
|
|
1091
1165
|
|
|
1092
1166
|
|
|
1093
1167
|
def _emit_command_output(
|
|
@@ -8026,6 +8100,11 @@ def audit_report_cmd(
|
|
|
8026
8100
|
format: str = typer.Option(
|
|
8027
8101
|
"json", "--format", "-f", help="Output format: json, yaml or markdown."
|
|
8028
8102
|
),
|
|
8103
|
+
from_risk: Optional[Path] = typer.Option(
|
|
8104
|
+
None,
|
|
8105
|
+
"--from-risk",
|
|
8106
|
+
help="Reuse an existing `ask risk -o risk.json` payload instead of recomputing risk.",
|
|
8107
|
+
),
|
|
8029
8108
|
copy: bool = _copy_option(),
|
|
8030
8109
|
) -> None:
|
|
8031
8110
|
"""[EXPERIMENTAL] Human audit bundle over existing ASK evidence, optionally signed.
|
|
@@ -8044,6 +8123,7 @@ def audit_report_cmd(
|
|
|
8044
8123
|
\b
|
|
8045
8124
|
Examples:
|
|
8046
8125
|
ask audit-report .
|
|
8126
|
+
ask risk . -o risk.json && ask audit-report . --from-risk risk.json
|
|
8047
8127
|
ask audit-report . --profile prod --format markdown
|
|
8048
8128
|
ask audit-report . --sign-key audit.key -o audit-report.json
|
|
8049
8129
|
"""
|
|
@@ -8079,12 +8159,42 @@ def audit_report_cmd(
|
|
|
8079
8159
|
expected="a non-empty key file",
|
|
8080
8160
|
)
|
|
8081
8161
|
raise typer.Exit(code=1)
|
|
8162
|
+
risk_payload = None
|
|
8163
|
+
if from_risk is not None:
|
|
8164
|
+
risk_path = Path(from_risk).expanduser()
|
|
8165
|
+
if not risk_path.is_file():
|
|
8166
|
+
_emit_error_json(
|
|
8167
|
+
INVALID_INPUT_CODE,
|
|
8168
|
+
f"--from-risk must name a readable risk JSON file (got {str(from_risk)!r}).",
|
|
8169
|
+
hint="Example: ask risk . -o risk.json && ask audit-report . --from-risk risk.json",
|
|
8170
|
+
expected="an existing JSON file produced by `ask risk`",
|
|
8171
|
+
)
|
|
8172
|
+
raise typer.Exit(code=1)
|
|
8173
|
+
try:
|
|
8174
|
+
risk_payload = json.loads(risk_path.read_text(encoding="utf-8"))
|
|
8175
|
+
except Exception as exc:
|
|
8176
|
+
_emit_error_json(
|
|
8177
|
+
INVALID_INPUT_CODE,
|
|
8178
|
+
f"--from-risk is not valid JSON: {exc}",
|
|
8179
|
+
hint="Regenerate it with: ask risk . -o risk.json",
|
|
8180
|
+
expected="a JSON object produced by `ask risk`",
|
|
8181
|
+
)
|
|
8182
|
+
raise typer.Exit(code=1)
|
|
8183
|
+
if not isinstance(risk_payload, dict) or "risks" not in risk_payload:
|
|
8184
|
+
_emit_error_json(
|
|
8185
|
+
INVALID_INPUT_CODE,
|
|
8186
|
+
"--from-risk does not look like an `ask risk` payload.",
|
|
8187
|
+
hint="Regenerate it with: ask risk . -o risk.json",
|
|
8188
|
+
expected="a JSON object with a `risks` array",
|
|
8189
|
+
)
|
|
8190
|
+
raise typer.Exit(code=1)
|
|
8082
8191
|
|
|
8083
8192
|
_prog = Progress()
|
|
8084
8193
|
_prog.start("packaging audit evidence")
|
|
8085
8194
|
try:
|
|
8086
8195
|
data = build_audit_report(
|
|
8087
8196
|
path, profiles=_profile_set(profile), sign_key=key_bytes,
|
|
8197
|
+
risk_payload=risk_payload,
|
|
8088
8198
|
)
|
|
8089
8199
|
finally:
|
|
8090
8200
|
_prog.stop()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""Lightweight
|
|
1
|
+
"""Lightweight progress indicator for long-running commands.
|
|
2
2
|
|
|
3
|
-
Writes only to stderr. Zero-cost when stderr is not a TTY or CI is detected
|
|
3
|
+
Writes only to stderr. Zero-cost when stderr is not a TTY or CI is detected,
|
|
4
|
+
unless ASK_PROGRESS=1 explicitly requests line heartbeats for supervised jobs.
|
|
4
5
|
Thread-safe; stop() is idempotent.
|
|
5
6
|
"""
|
|
6
7
|
from __future__ import annotations
|
|
@@ -15,6 +16,8 @@ _FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def _terminal_capable() -> bool:
|
|
19
|
+
if os.environ.get("ASK_PROGRESS", "").lower() in ("1", "true", "yes", "on"):
|
|
20
|
+
return True
|
|
18
21
|
if not sys.stderr.isatty():
|
|
19
22
|
return False
|
|
20
23
|
if os.environ.get("TERM") in ("dumb", ""):
|
|
@@ -43,6 +46,11 @@ class Progress:
|
|
|
43
46
|
|
|
44
47
|
def __init__(self) -> None:
|
|
45
48
|
self._enabled = _terminal_capable()
|
|
49
|
+
self._line_mode = self._enabled and not sys.stderr.isatty()
|
|
50
|
+
try:
|
|
51
|
+
self._interval = max(0.5, float(os.environ.get("ASK_PROGRESS_INTERVAL", "5")))
|
|
52
|
+
except ValueError:
|
|
53
|
+
self._interval = 5.0
|
|
46
54
|
self._phase = ""
|
|
47
55
|
self._t0 = time.monotonic()
|
|
48
56
|
self._lock = threading.Lock()
|
|
@@ -81,7 +89,8 @@ class Progress:
|
|
|
81
89
|
self._stop_event.set()
|
|
82
90
|
if self._thread:
|
|
83
91
|
self._thread.join(timeout=1.0)
|
|
84
|
-
|
|
92
|
+
if not self._line_mode:
|
|
93
|
+
sys.stderr.write("\r\033[K")
|
|
85
94
|
sys.stderr.flush()
|
|
86
95
|
return elapsed
|
|
87
96
|
|
|
@@ -91,17 +100,23 @@ class Progress:
|
|
|
91
100
|
if not self._enabled:
|
|
92
101
|
return
|
|
93
102
|
t = f"{elapsed:.1f}s" if elapsed < 60 else f"{elapsed / 60:.1f}m"
|
|
94
|
-
|
|
103
|
+
prefix = "[ask] " if self._line_mode else "✓ "
|
|
104
|
+
sys.stderr.write(f"{prefix}done ({t})\n")
|
|
95
105
|
sys.stderr.flush()
|
|
96
106
|
|
|
97
107
|
def _loop(self) -> None:
|
|
98
108
|
idx = 0
|
|
99
|
-
|
|
109
|
+
interval = self._interval if self._line_mode else 0.08
|
|
110
|
+
while not self._stop_event.wait(timeout=interval):
|
|
100
111
|
frame = _FRAMES[idx % len(_FRAMES)]
|
|
101
112
|
elapsed = time.monotonic() - self._t0
|
|
102
113
|
with self._lock:
|
|
103
114
|
phase = self._phase
|
|
104
|
-
|
|
115
|
+
if self._line_mode:
|
|
116
|
+
t = f"{elapsed:.1f}s" if elapsed < 60 else f"{elapsed / 60:.1f}m"
|
|
117
|
+
line = f"[ask] progress phase={phase} elapsed={t}\n"
|
|
118
|
+
else:
|
|
119
|
+
line = f"\r{frame} {phase} ({elapsed:.1f}s)"
|
|
105
120
|
try:
|
|
106
121
|
sys.stderr.write(line)
|
|
107
122
|
sys.stderr.flush()
|
|
@@ -162,6 +162,7 @@ def build_evidence_manifest(
|
|
|
162
162
|
command: str,
|
|
163
163
|
root: Path,
|
|
164
164
|
inputs: Optional[dict[str, Any]] = None,
|
|
165
|
+
include_generated_at: bool = True,
|
|
165
166
|
) -> dict[str, Any]:
|
|
166
167
|
"""Build a machine-readable provenance index for an existing payload."""
|
|
167
168
|
root = Path(root).resolve()
|
|
@@ -173,17 +174,19 @@ def build_evidence_manifest(
|
|
|
173
174
|
for claim in claims
|
|
174
175
|
if (claim.get("source") or {}).get("path")
|
|
175
176
|
})
|
|
177
|
+
run: dict[str, Any] = {
|
|
178
|
+
"command": command,
|
|
179
|
+
"tool": "ask",
|
|
180
|
+
"tool_version": __version__,
|
|
181
|
+
"repository": str(root),
|
|
182
|
+
"git_head": _git_head(root),
|
|
183
|
+
"inputs": inputs or {},
|
|
184
|
+
}
|
|
185
|
+
if include_generated_at:
|
|
186
|
+
run["generated_at"] = datetime.now(timezone.utc).isoformat()
|
|
176
187
|
return {
|
|
177
188
|
"schema_version": PROVENANCE_SCHEMA,
|
|
178
|
-
"run":
|
|
179
|
-
"command": command,
|
|
180
|
-
"tool": "ask",
|
|
181
|
-
"tool_version": __version__,
|
|
182
|
-
"repository": str(root),
|
|
183
|
-
"git_head": _git_head(root),
|
|
184
|
-
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
185
|
-
"inputs": inputs or {},
|
|
186
|
-
},
|
|
189
|
+
"run": run,
|
|
187
190
|
"claims": claims,
|
|
188
191
|
"sources": [{"path": path} for path in sources],
|
|
189
192
|
"claims_cap": (
|
|
@@ -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-05"
|
|
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
|
|
@@ -6975,6 +6975,27 @@ def compute_blast_radius(
|
|
|
6975
6975
|
)
|
|
6976
6976
|
except Exception:
|
|
6977
6977
|
_impl_graph = None
|
|
6978
|
+
if _impl_graph is not None:
|
|
6979
|
+
_class_fqns = {
|
|
6980
|
+
n.get("fqn") or ""
|
|
6981
|
+
for n in graph_nodes
|
|
6982
|
+
if (n.get("symbol_kind") or n.get("type"))
|
|
6983
|
+
in {"class", "interface", "enum", "record", "annotation"}
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
def _seed_class(_sym: str) -> str:
|
|
6987
|
+
return _sym if _sym in _class_fqns else _enclosing_class(_sym)
|
|
6988
|
+
|
|
6989
|
+
_seed_set = set(matched_fqns)
|
|
6990
|
+
_expanded = set(matched_fqns)
|
|
6991
|
+
_symbols_by_class: dict[str, list[str]] = {}
|
|
6992
|
+
for _sym in reverse_graph:
|
|
6993
|
+
_symbols_by_class.setdefault(_seed_class(_sym), []).append(_sym)
|
|
6994
|
+
for _seed in sorted(_seed_set):
|
|
6995
|
+
for _subtype in getattr(_impl_graph, "all_subtypes_of")(_seed_class(_seed)):
|
|
6996
|
+
for _sym in _symbols_by_class.get(_subtype, ()):
|
|
6997
|
+
_expanded.add(_sym)
|
|
6998
|
+
matched_fqns = _expanded
|
|
6978
6999
|
for seed in matched_fqns:
|
|
6979
7000
|
_seed_callers = _all_callers_from_rg(seed, reverse_graph, _rg_index, _impl_graph)
|
|
6980
7001
|
if len(_seed_callers) > _HUB_CALLER_THRESHOLD and max_depth > 1:
|