sourcecode 4.10.7__tar.gz → 4.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-4.10.7 → sourcecode-4.12.0}/CHANGELOG.md +180 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/PKG-INFO +23 -7
- {sourcecode-4.10.7 → sourcecode-4.12.0}/README.md +22 -6
- {sourcecode-4.10.7 → sourcecode-4.12.0}/pyproject.toml +1 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/architectural_baseline.py +45 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/cache_model.py +100 -18
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/canonical_ir.py +8 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/cli.py +737 -186
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/container_wiring.py +21 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/context_cache.py +9 -3
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/context_graph.py +8 -3
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detach.py +27 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/format_contract.py +48 -6
- sourcecode-4.12.0/src/sourcecode/mcp/dependency_import.py +87 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/registry.py +6 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/server.py +20 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/non_coverage.py +90 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/parse_cache.py +166 -7
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/phased_run.py +61 -10
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/progress.py +141 -4
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/readiness_timeline.py +10 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/release_info.py +1 -1
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/repository_ir.py +37 -5
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_impact.py +5 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/risk.py +120 -38
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/rule_catalog.py +5 -1
- sourcecode-4.12.0/src/sourcecode/rule_pass.py +112 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_impact.py +29 -3
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_security_audit.py +49 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_tx_analyzer.py +34 -2
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/verify_repo.py +59 -9
- {sourcecode-4.10.7 → sourcecode-4.12.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/.gitignore +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/.ruff.toml +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/CLAUDE.md +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/CONTRIBUTING.md +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/LICENSE +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/SECURITY.md +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/raw +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/scripts/perf_harness.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/audit_report.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/client_calls.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/data_exposure.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/execution_plan.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/license.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/output_encoding.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/provenance.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/remedies.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/runs.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/security_config_scan.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/servlet_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/source_text.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/text_input.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/functions/README.md +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.10.7 → sourcecode-4.12.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,186 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.12.0] — 2026-08-08
|
|
6
|
+
|
|
7
|
+
**Field evaluation #16 — the third round on the same repository and commit, and the
|
|
8
|
+
first A/B/C of three of our own releases (4.10.6 → 4.10.7 → 4.11.0, 7,0 → 7,0 →
|
|
9
|
+
6,5).** The three value axes did not move: correctness 9,5, unique value 9,0,
|
|
10
|
+
epistemic honesty 10, with `endpoints.json`, `servlets.json` and `validation.json`
|
|
11
|
+
**byte-identical across all three builds**. What fell is engineering, and the
|
|
12
|
+
sentence is about us: *"no es que haya regresiones — es que no hay nada que las
|
|
13
|
+
detecte."* Nine rows close here, one commit each.
|
|
14
|
+
|
|
15
|
+
**Behaviour changes to read before upgrading a pipeline:**
|
|
16
|
+
|
|
17
|
+
- **`risk_score` and `risk_score_raw` are `null`, not `0.0`, when the container
|
|
18
|
+
invokes the queried component** (`@Configuration`, `@Aspect`, a registered
|
|
19
|
+
filter). The band already said `high` for these while the number said `0.0` — the
|
|
20
|
+
worst possible value for the most critical symbol in a system, from the same
|
|
21
|
+
object. `risk_score_basis` says why and names the surfaces that do measure such a
|
|
22
|
+
component. Affects `impact-chain`, `impact`, `plan` and `compare`; a consumer
|
|
23
|
+
reading the field with a `0.0` default now sees `null`.
|
|
24
|
+
- **`ASK_MAX_ANALYSIS_SECONDS` actually stops a `spring-audit` rule pass.** It was
|
|
25
|
+
only ever checked between the three phases, and the last one holds ~85 % of the
|
|
26
|
+
time, so a 420 s budget produced a 2 351 s run and no `partial` anywhere. A cut
|
|
27
|
+
run now publishes `metadata.rules_not_run`, a `PARTIAL:` limitation, and
|
|
28
|
+
`_partial.phases_stopped_inside`. `metadata.rules_run` is present on every run.
|
|
29
|
+
- **`migrate-check` no longer creates `<scope>/.ask/` unless you pass `--snapshot`.**
|
|
30
|
+
A read-only run left a git-ignored directory in the audited repository — invisible
|
|
31
|
+
to `git status`, visible only to `git status --ignored`.
|
|
32
|
+
- **`verify` gains `--allow-unverified`** (exit 0 while the verdict stays
|
|
33
|
+
`unverified`) and publishes `unverified_reason`: `no_contracts_declared` or
|
|
34
|
+
`gate_could_not_run`. Exit codes are unchanged; the remedy is now the first reason.
|
|
35
|
+
- **The parse cache retires generations.** Entries live under the producing
|
|
36
|
+
analyzer's fingerprint, and a run purges the generations that can never be read
|
|
37
|
+
again — the field measured +142,53 MB per release, three copies of one repository,
|
|
38
|
+
427,59 MB after three runs. `cache status` publishes the composition.
|
|
39
|
+
|
|
40
|
+
**Also in this release:**
|
|
41
|
+
|
|
42
|
+
- The rule pass reports the family it is on, counted against the total, and names
|
|
43
|
+
the two stages that have no population (`spring-audit` and `risk`). The ETA is
|
|
44
|
+
withheld there on purpose: rule families are not comparable units.
|
|
45
|
+
- `risk` checkpoints — after the audit and every 25 composed rows — with the partial
|
|
46
|
+
file built by the same assembler as the answer, so it is the same document over
|
|
47
|
+
fewer rows.
|
|
48
|
+
- The budget message stops quoting a class constant in the grammar of a
|
|
49
|
+
measurement. It carries this command's measured anchors and the size of the
|
|
50
|
+
repository in hand, and says plainly that no projection is offered. Anchors
|
|
51
|
+
recalibrated: `spring-audit` 408 s → 2 351 s at 3 342 files, `endpoints` 4,2 → 5,0,
|
|
52
|
+
`migrate-check --compact` 26,7 and `posture` 38,0 (both previously recorded as not
|
|
53
|
+
finishing), `risk` 3 232.
|
|
54
|
+
- The MCP stack's incomplete-model warning no longer reaches stderr, and the models
|
|
55
|
+
it complains about are rebuilt rather than only muted.
|
|
56
|
+
- A 772-line temp file (`cache.tmp_new`) was shipping inside the wheel; removed, and
|
|
57
|
+
the package contents are now asserted by allow-list.
|
|
58
|
+
|
|
59
|
+
**Measured and not fixed, deliberately:** the block performance regression the
|
|
60
|
+
evaluation reports (seven commands 2–3,4× slower on a minor) **does not reproduce
|
|
61
|
+
here** — the A/B of 4.10.7 against 4.11.0 on a 2 985-file repository lands within
|
|
62
|
+
1 % on six commands, with `posture --resolve-environments` byte-identical, and
|
|
63
|
+
`risk` could not be A/B'd because the *4.10.7* side alone passed 61 minutes. Its
|
|
64
|
+
cost is structural, not new. The numbers are in the ledger; the row waits on a
|
|
65
|
+
Windows runner rather than on a guess.
|
|
66
|
+
|
|
67
|
+
## [4.11.0] — 2026-08-07
|
|
68
|
+
|
|
69
|
+
**The queue behind Tier D: five ledger rows, and three batteries that were red in
|
|
70
|
+
the release before this one.** C3-59 · C3-56 (second half) · C3-52 · C3-60 (second
|
|
71
|
+
half) · CL-15 close here, one commit each. Two of them were bigger than the row
|
|
72
|
+
said, and both in the same direction — a flag that parses and does not check, a
|
|
73
|
+
message that promises output nobody writes:
|
|
74
|
+
|
|
75
|
+
- **`--format` was published by eleven commands that validated nothing**, so
|
|
76
|
+
`ask risk . --format bogus` answered with JSON as if the value had counted
|
|
77
|
+
(C3-59);
|
|
78
|
+
- **`--detach` told the operator "progress and errors go to `log`"** and the log
|
|
79
|
+
held one line, because a detached worker has no terminal and the heartbeat
|
|
80
|
+
switched itself off exactly where it is the only way to tell work from a hang
|
|
81
|
+
(C3-52).
|
|
82
|
+
|
|
83
|
+
⚠ **Behaviour changes to read before upgrading a pipeline:**
|
|
84
|
+
|
|
85
|
+
- **An unsupported `--format` now exits 2** with the values that are valid, on
|
|
86
|
+
the eleven commands that used to accept anything and answer JSON. A script
|
|
87
|
+
passing a value that was never supported fails where it used to appear to work.
|
|
88
|
+
- **`--format/-f` is accepted after 34 more commands**, including the 29
|
|
89
|
+
`retrieve` intents, `modernize`, `schema`, `onboard` and `fix-bug`.
|
|
90
|
+
- **`ask config <path>` refuses a path that does not exist** (exit 1, the standard
|
|
91
|
+
envelope) instead of reporting "nothing is declared" about it.
|
|
92
|
+
- **A detached run writes heartbeat lines into its own log** unless `--progress`
|
|
93
|
+
or `ASK_PROGRESS` says otherwise. Nothing changes for a foreground, piped or CI
|
|
94
|
+
run that does not ask.
|
|
95
|
+
|
|
96
|
+
Three tests were **already red in 4.10.7** — each a battery that derives its
|
|
97
|
+
population from the CLI, so C4-17's and C4-20's own changes tripped them — and
|
|
98
|
+
they are fixed here. One of the three was not a test defect: it found `ask config`
|
|
99
|
+
answering about a repository that does not exist.
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
|
|
103
|
+
- **The `spring-audit` security section says what it is before it says what it
|
|
104
|
+
found** (CL-15). A field evaluation found the repository's critical SQL
|
|
105
|
+
injection by hand and ASK did not report it — correctly and by declaration
|
|
106
|
+
(NC-001: no dataflow) — but the section is announced as a *security surface*,
|
|
107
|
+
which invites reading it as a defect finder. The surface now states, from one
|
|
108
|
+
table rendered into both `--help` and the payload, that it reads the security
|
|
109
|
+
**configuration and exposure** (declared chain rules, open endpoints, disabled
|
|
110
|
+
controls, bypassable gates, each with file and line), that it is **not** a
|
|
111
|
+
vulnerability scanner, and where it is strongest: given a defect somebody else
|
|
112
|
+
found, `ask impact` and `ask risk` quantify what it reaches. The rule-family
|
|
113
|
+
label follows (`security surface` → `security configuration and exposure`). No
|
|
114
|
+
rule and no finding changed — this was always a naming defect.
|
|
115
|
+
|
|
116
|
+
### Fixed
|
|
117
|
+
|
|
118
|
+
- **`ask config <path>` refuses a repository nobody could read** instead of
|
|
119
|
+
reporting on it (C4-21 follow-up). The command gained a repository argument in
|
|
120
|
+
4.10.7 without the admission gate every other path-taking command uses, so
|
|
121
|
+
`ask config /nonexistent` answered *"Declaration: none at
|
|
122
|
+
/nonexistent/sourcecode.config.json"* with exit 0 — and *"nothing is declared
|
|
123
|
+
here"* is indistinguishable from *"nobody could look"*, which is the reading
|
|
124
|
+
ADR-0008 exists to prevent.
|
|
125
|
+
|
|
126
|
+
- **`ask cache model` states which repository its figures were measured on, and
|
|
127
|
+
what that means for yours** (C3-60, second half). It published `verify-edit` as
|
|
128
|
+
*14,6 s → 9,6 s → 5,6 s* and called it *"built for the edit loop"* while the
|
|
129
|
+
same command had not answered at 495 s on the field repository — both
|
|
130
|
+
measurements, one printed. The command now measures the scope it is run in
|
|
131
|
+
(same authority as `--help` and the remedies), says whether the published
|
|
132
|
+
figures transfer to it, and gives each command a `here:` line saying how it can
|
|
133
|
+
be run on *this* repository. No figure changed and nothing is projected: an
|
|
134
|
+
unmeasurable scope gets the reference figures unqualified, as before.
|
|
135
|
+
|
|
136
|
+
- **`--progress off | stderr | file:<path>` on every command that can make you
|
|
137
|
+
wait** (C3-52). The heartbeat existed and the watcher model everybody uses
|
|
138
|
+
could not see it: a supervisor reading the standard streams got nothing for
|
|
139
|
+
703 seconds unless it knew to set `ASK_PROGRESS=1` or to poll the run record —
|
|
140
|
+
knowledge that appeared in no `--help` and in no payload. The choice is now
|
|
141
|
+
published on the command being typed (62 commands, population from the
|
|
142
|
+
registry), `ASK_PROGRESS` still sets the same thing in the environment, and an
|
|
143
|
+
unrecognised value is refused with exit 2 rather than silently falling back to
|
|
144
|
+
the default. Nothing changes for a piped or CI run that does not ask.
|
|
145
|
+
- **A detached run's log carries the progress its own envelope promises**
|
|
146
|
+
(C3-52). `--detach` printed *"Progress and errors go to `log`"* and the log
|
|
147
|
+
held one line, because a detached worker has no terminal and the heartbeat
|
|
148
|
+
disabled itself exactly where it is the only way to tell work from a hang. A
|
|
149
|
+
detached run now enables the heartbeat into its own log unless `--progress` or
|
|
150
|
+
`ASK_PROGRESS` says otherwise; no shell reads those bytes, so the PowerShell
|
|
151
|
+
`$?` problem (C3-66) cannot be reached from a detached run.
|
|
152
|
+
|
|
153
|
+
- **The progress heartbeat carries a denominator and an ETA** (C3-56, second
|
|
154
|
+
half). `phase=auditing 3342 Java files elapsed=6.8m` said the run was alive and
|
|
155
|
+
nothing about whether it was worth waiting for; the field's only way to tell
|
|
156
|
+
work from a hang was reading the worker's CPU time. The line now reads
|
|
157
|
+
`phase=auditing 2766 Java files stage=parsing 1458/2766 files elapsed=2.0s
|
|
158
|
+
eta=1.8s eta_covers=parsing`, with the counts coming from the loops that read
|
|
159
|
+
the files rather than from the spinner. The ETA **names what it covers**
|
|
160
|
+
(nothing here knows the cost of the stages after the counted one), is withheld
|
|
161
|
+
until it can be measured, and is dropped when the phase changes rather than
|
|
162
|
+
freezing at `2766/2766`. A run with no heartbeat enabled is not asked to count.
|
|
163
|
+
|
|
164
|
+
- **`-f` means `--format` on every command that publishes it, and every command
|
|
165
|
+
that answers with a payload document publishes it** (C3-59). `-f json` was
|
|
166
|
+
accepted by `endpoints` and `spring-audit` and refused by `modernize`, whose
|
|
167
|
+
error called `-f` a global flag *"as `ask -f <path>`"* — so the message taught
|
|
168
|
+
the collision it was meant to remove. The population is now the C4-17 rule one
|
|
169
|
+
level along (a payload command publishes `--format/-f`; a command that answers
|
|
170
|
+
in lines is exempt on the record in `cli.COMMANDS_WITHOUT_FORMAT`), and 34
|
|
171
|
+
commands gained the flag: `modernize`, `archetype`, `cold-start`, `schema`,
|
|
172
|
+
`onboard`, `fix-bug` and the 29 `retrieve` intents. One conversion in the emit
|
|
173
|
+
seam serves all of them, on the *stamped* document, so a YAML answer carries
|
|
174
|
+
the same `_meta` as the JSON one.
|
|
175
|
+
- **An unsupported `--format` is refused instead of silently ignored** (C3-59).
|
|
176
|
+
Eleven commands published the flag and validated nothing — `ask risk .
|
|
177
|
+
--format bogus` answered with JSON as if the value had counted. Validation
|
|
178
|
+
moved into the shared option factory, keyed off the command being run, so a
|
|
179
|
+
command cannot publish the flag and skip the check;
|
|
180
|
+
`sourcecode.format_contract.FORMAT_REGISTRY` is the one authority for what
|
|
181
|
+
each command accepts, and an invalid value exits 2 with the values that are
|
|
182
|
+
valid. `rename-class` keeps `-f` for `--from` (on the record in
|
|
183
|
+
`COMMANDS_WITHOUT_FORMAT_SHORTHAND`); its long `--format` is unchanged.
|
|
184
|
+
|
|
5
185
|
## [4.10.7] — 2026-08-07
|
|
6
186
|
|
|
7
187
|
**Tier D, and with it the M13 bug queue.** Five ledger rows close (C4-17 · C4-18 ·
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.12.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -73,8 +73,10 @@ These numbers are cache-layer benchmarks, not a promise for every command. Per-s
|
|
|
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
75
|
|
|
76
|
-
**Large-repo controls.**
|
|
77
|
-
|
|
76
|
+
**Large-repo controls.** Pass `--progress stderr` (or `--progress file:run.log`)
|
|
77
|
+
for line heartbeats in a non-TTY supervised run — with the file being read so
|
|
78
|
+
far and an ETA for the stage being measured. `ASK_PROGRESS` sets the same thing
|
|
79
|
+
in the environment. Long repo-wide/deep runs also write durable records under
|
|
78
80
|
`~/.sourcecode/runs/<repo-hash>/` by default, not inside the audited repository;
|
|
79
81
|
set `ASK_RUNS_IN_REPO=1` only when repo-local `.ask/runs` records are intentional.
|
|
80
82
|
Set `ASK_MAX_ANALYSIS_SECONDS=<seconds>` to fail before starting a repo-wide/deep
|
|
@@ -106,7 +108,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
106
108
|
# pip / pipx
|
|
107
109
|
pipx install sourcecode # or: pip install sourcecode
|
|
108
110
|
|
|
109
|
-
ask version # ask 4.
|
|
111
|
+
ask version # ask 4.12.0 — and, on a build that has aged,
|
|
110
112
|
# how many releases have probably shipped since
|
|
111
113
|
```
|
|
112
114
|
|
|
@@ -322,7 +324,7 @@ from it.
|
|
|
322
324
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
323
325
|
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
324
326
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
325
|
-
| `spring-audit` | core | transactional anomalies + security
|
|
327
|
+
| `spring-audit` | core | transactional anomalies + security configuration/exposure + validation gaps | `--ci`, `-f github-comment`; `--table --rule SEC-008 --top-n 20` for human-scale review |
|
|
326
328
|
| `migrate-check` | core | Boot 2→3 readiness + Java LTS/licensing inventory: located blockers, per-dimension score, effort, detected Java 8/11/17/21/25 evidence and explicit licensing-review signals | `--target-jdk 25` sets the target LTS route; `--blast-radius` orders the re-test plan; `--table --rule MIG-001 --band critical --top-n 20` for blocker triage |
|
|
327
329
|
| `impact` / `impact-chain` | core | blast radius of a change, to the endpoints it reaches | target the **interface**, not the `Impl` |
|
|
328
330
|
| `pr-impact` | core | the same, scoped to a PR diff | gating command: `--fail-on`, exit codes |
|
|
@@ -359,7 +361,21 @@ Honest limits worth knowing before you rely on it:
|
|
|
359
361
|
- Architecture classification is tuned for Spring MVC layered apps; SPI/plugin models (e.g. Quarkus extensions) may be misclassified. JAX-RS subresource-locator endpoint recall is ~65%.
|
|
360
362
|
- Self-invocation `@Transactional` bypass (same-class call skipping the proxy) is not detected.
|
|
361
363
|
|
|
362
|
-
### What the security
|
|
364
|
+
### What the security section is, and what it does not answer
|
|
365
|
+
|
|
366
|
+
**What it reads:** the security *configuration and exposure* of a repository — which
|
|
367
|
+
rules the request chain declares, which endpoints they leave open, which controls are
|
|
368
|
+
present but disabled, and which custom gates can be bypassed, each with the file and
|
|
369
|
+
line that says so.
|
|
370
|
+
|
|
371
|
+
**What it is not:** a vulnerability scanner. No value flows through a program here, so
|
|
372
|
+
an injection, a deserialization sink or a logic flaw inside a method body is not looked
|
|
373
|
+
for, and its absence from the report is not evidence (NC-001, below).
|
|
374
|
+
|
|
375
|
+
**Where it is strongest:** given a defect somebody else found — a scanner finding, a
|
|
376
|
+
line from a review — `ask impact <symbol>` and `ask risk` quantify what it reaches: the
|
|
377
|
+
endpoints, the transactional boundaries and the modules downstream of it, with the
|
|
378
|
+
confidence of each hop. That is the question a taint scanner does not answer.
|
|
363
379
|
|
|
364
380
|
Published because a product that states its limits is not compared on breadth — it is
|
|
365
381
|
compared on depth. Each row is emitted in the payload too (`non_coverage`), so an agent
|
|
@@ -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`**
|
|
@@ -35,8 +35,10 @@ These numbers are cache-layer benchmarks, not a promise for every command. Per-s
|
|
|
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
37
|
|
|
38
|
-
**Large-repo controls.**
|
|
39
|
-
|
|
38
|
+
**Large-repo controls.** Pass `--progress stderr` (or `--progress file:run.log`)
|
|
39
|
+
for line heartbeats in a non-TTY supervised run — with the file being read so
|
|
40
|
+
far and an ETA for the stage being measured. `ASK_PROGRESS` sets the same thing
|
|
41
|
+
in the environment. Long repo-wide/deep runs also write durable records under
|
|
40
42
|
`~/.sourcecode/runs/<repo-hash>/` by default, not inside the audited repository;
|
|
41
43
|
set `ASK_RUNS_IN_REPO=1` only when repo-local `.ask/runs` records are intentional.
|
|
42
44
|
Set `ASK_MAX_ANALYSIS_SECONDS=<seconds>` to fail before starting a repo-wide/deep
|
|
@@ -68,7 +70,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
68
70
|
# pip / pipx
|
|
69
71
|
pipx install sourcecode # or: pip install sourcecode
|
|
70
72
|
|
|
71
|
-
ask version # ask 4.
|
|
73
|
+
ask version # ask 4.12.0 — and, on a build that has aged,
|
|
72
74
|
# how many releases have probably shipped since
|
|
73
75
|
```
|
|
74
76
|
|
|
@@ -284,7 +286,7 @@ from it.
|
|
|
284
286
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
285
287
|
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
286
288
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
287
|
-
| `spring-audit` | core | transactional anomalies + security
|
|
289
|
+
| `spring-audit` | core | transactional anomalies + security configuration/exposure + validation gaps | `--ci`, `-f github-comment`; `--table --rule SEC-008 --top-n 20` for human-scale review |
|
|
288
290
|
| `migrate-check` | core | Boot 2→3 readiness + Java LTS/licensing inventory: located blockers, per-dimension score, effort, detected Java 8/11/17/21/25 evidence and explicit licensing-review signals | `--target-jdk 25` sets the target LTS route; `--blast-radius` orders the re-test plan; `--table --rule MIG-001 --band critical --top-n 20` for blocker triage |
|
|
289
291
|
| `impact` / `impact-chain` | core | blast radius of a change, to the endpoints it reaches | target the **interface**, not the `Impl` |
|
|
290
292
|
| `pr-impact` | core | the same, scoped to a PR diff | gating command: `--fail-on`, exit codes |
|
|
@@ -321,7 +323,21 @@ Honest limits worth knowing before you rely on it:
|
|
|
321
323
|
- Architecture classification is tuned for Spring MVC layered apps; SPI/plugin models (e.g. Quarkus extensions) may be misclassified. JAX-RS subresource-locator endpoint recall is ~65%.
|
|
322
324
|
- Self-invocation `@Transactional` bypass (same-class call skipping the proxy) is not detected.
|
|
323
325
|
|
|
324
|
-
### What the security
|
|
326
|
+
### What the security section is, and what it does not answer
|
|
327
|
+
|
|
328
|
+
**What it reads:** the security *configuration and exposure* of a repository — which
|
|
329
|
+
rules the request chain declares, which endpoints they leave open, which controls are
|
|
330
|
+
present but disabled, and which custom gates can be bypassed, each with the file and
|
|
331
|
+
line that says so.
|
|
332
|
+
|
|
333
|
+
**What it is not:** a vulnerability scanner. No value flows through a program here, so
|
|
334
|
+
an injection, a deserialization sink or a logic flaw inside a method body is not looked
|
|
335
|
+
for, and its absence from the report is not evidence (NC-001, below).
|
|
336
|
+
|
|
337
|
+
**Where it is strongest:** given a defect somebody else found — a scanner finding, a
|
|
338
|
+
line from a review — `ask impact <symbol>` and `ask risk` quantify what it reaches: the
|
|
339
|
+
endpoints, the transactional boundaries and the modules downstream of it, with the
|
|
340
|
+
confidence of each hop. That is the question a taint scanner does not answer.
|
|
325
341
|
|
|
326
342
|
Published because a product that states its limits is not compared on breadth — it is
|
|
327
343
|
compared on depth. Each row is emitted in the payload too (`non_coverage`), so an agent
|
|
@@ -30,6 +30,7 @@ the perf baselines do.
|
|
|
30
30
|
from __future__ import annotations
|
|
31
31
|
|
|
32
32
|
import json
|
|
33
|
+
import os
|
|
33
34
|
import subprocess
|
|
34
35
|
from datetime import datetime, timezone
|
|
35
36
|
from pathlib import Path
|
|
@@ -365,21 +366,40 @@ def _baseline_filename(baseline: dict) -> str:
|
|
|
365
366
|
#: a 14 587-entry structural inventory of the code. A self-confined
|
|
366
367
|
#: `.ask/.gitignore` is the honest form: it never touches the user's own
|
|
367
368
|
#: `.gitignore`, and it stops applying the moment they delete the directory.
|
|
369
|
+
#: C3-74 corrected the last line. It used to read *"Delete this file to version
|
|
370
|
+
#: them deliberately"*, and deleting it is the one action that would commit a
|
|
371
|
+
#: baseline carrying `env.hostname_hash`, `cpu` and `cores` — an invitation to do
|
|
372
|
+
#: the thing the file exists to prevent. It now states the consequence instead.
|
|
368
373
|
_ASK_DIR_GITIGNORE = (
|
|
369
374
|
"# Written by `ask` the first time it created this directory.\n"
|
|
370
375
|
"# These are local analysis artefacts: a structural inventory of your code\n"
|
|
371
376
|
"# and, if you asked for it, the machine that measured it. Nothing here\n"
|
|
372
|
-
"# belongs in a commit
|
|
377
|
+
"# belongs in a commit.\n"
|
|
378
|
+
"#\n"
|
|
379
|
+
"# Removing this file makes everything here committable, including any\n"
|
|
380
|
+
"# recorded machine fingerprint (`--record-env`: hostname hash, CPU, cores).\n"
|
|
381
|
+
"# Version these deliberately only if you intend to publish that.\n"
|
|
373
382
|
"*\n"
|
|
374
383
|
)
|
|
375
384
|
|
|
376
385
|
|
|
377
|
-
def protect_ask_dir(out_dir: Path) -> None:
|
|
386
|
+
def protect_ask_dir(out_dir: Path, *, announce: bool = True) -> None:
|
|
378
387
|
"""Give a freshly created `.ask/` its own `.gitignore` (C3-68).
|
|
379
388
|
|
|
380
389
|
Best-effort and never overwriting: a user who edited it decided something,
|
|
381
390
|
and a tool that re-imposes its default on every run is worse than one that
|
|
382
391
|
never wrote it.
|
|
392
|
+
|
|
393
|
+
**Call this where the write happens, never where the path is computed.** This
|
|
394
|
+
function *creates* the directory in order to protect it, so calling it
|
|
395
|
+
speculatively is a write — which is how `migrate-check` came to leave a
|
|
396
|
+
`.ask/.gitignore` inside a repository it had only read (C3-74).
|
|
397
|
+
|
|
398
|
+
`announce` puts one line on stderr the first time the directory is created,
|
|
399
|
+
with its path. A side effect inside somebody else's repository is exactly what
|
|
400
|
+
I-8 says must be declared, and this one hides from the check an auditor makes:
|
|
401
|
+
`git status --porcelain` is clean because the ignore file ignores itself, and
|
|
402
|
+
only `--ignored` reveals `!! <scope>/.ask/`.
|
|
383
403
|
"""
|
|
384
404
|
try:
|
|
385
405
|
ask_dir = Path(out_dir)
|
|
@@ -391,12 +411,35 @@ def protect_ask_dir(out_dir: Path) -> None:
|
|
|
391
411
|
return
|
|
392
412
|
marker = ask_dir / ".gitignore"
|
|
393
413
|
if not marker.exists():
|
|
414
|
+
created = not ask_dir.exists()
|
|
394
415
|
ask_dir.mkdir(parents=True, exist_ok=True)
|
|
395
416
|
marker.write_text(_ASK_DIR_GITIGNORE, encoding="utf-8")
|
|
417
|
+
if announce and created:
|
|
418
|
+
_announce_ask_dir(ask_dir)
|
|
396
419
|
except OSError:
|
|
397
420
|
return
|
|
398
421
|
|
|
399
422
|
|
|
423
|
+
def _announce_ask_dir(ask_dir: Path) -> None:
|
|
424
|
+
"""One line, on stderr, naming what was created and how to move it (C3-74)."""
|
|
425
|
+
import sys
|
|
426
|
+
|
|
427
|
+
try:
|
|
428
|
+
# No flag is named here on purpose: the commands that write under `.ask/`
|
|
429
|
+
# spell the override differently (`baseline capture --dir`,
|
|
430
|
+
# `migrate-check --history-dir`), and a message that names the wrong one
|
|
431
|
+
# is the class of defect this ledger is about.
|
|
432
|
+
sys.stderr.write(
|
|
433
|
+
f"[ask] created {ask_dir}{os.sep} for this run's local artefacts — "
|
|
434
|
+
f"git-ignored by its own .gitignore, so `git status` will not show it "
|
|
435
|
+
f"(`git status --ignored` will). The command's directory option takes a "
|
|
436
|
+
f"path outside the repository if you want them elsewhere.\n"
|
|
437
|
+
)
|
|
438
|
+
sys.stderr.flush()
|
|
439
|
+
except Exception:
|
|
440
|
+
pass
|
|
441
|
+
|
|
442
|
+
|
|
400
443
|
def write_baseline(baseline: dict, out_dir: Path) -> Path:
|
|
401
444
|
"""Write `baseline` as deterministic JSON under `out_dir`; return the file path."""
|
|
402
445
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -75,10 +75,17 @@ class CommandCache:
|
|
|
75
75
|
warm_seconds: "Optional[float]" = None
|
|
76
76
|
#: The other measured point: this command on the field repository
|
|
77
77
|
#: (3 342 Java files, Windows 11 / PowerShell 5.1 / pipx, cold), from field
|
|
78
|
-
#: evaluations #13 and #
|
|
78
|
+
#: evaluations #13, #14 and #16. Seconds where a run finished; `field_blocked`
|
|
79
79
|
#: where it did not — the harness promoted the process and the session died,
|
|
80
80
|
#: which is a measurement of a different kind and is never written as a
|
|
81
81
|
#: duration. Rows with neither were not attempted there.
|
|
82
|
+
#:
|
|
83
|
+
#: C3-72: where two evaluations of the same command at this size disagree, the
|
|
84
|
+
#: figure here is the **most recent** one, because it describes the build a
|
|
85
|
+
#: reader is about to run — and the previous figure is kept in the comment
|
|
86
|
+
#: beside it rather than dropped, since a cost that moved by 5,8× between two
|
|
87
|
+
#: releases (spring-audit, 408 s in #13 against 2 351 s in #16) is itself the
|
|
88
|
+
#: measurement C3-76 exists for.
|
|
82
89
|
field_seconds: "Optional[float]" = None
|
|
83
90
|
field_blocked: bool = False
|
|
84
91
|
|
|
@@ -93,19 +100,26 @@ REFERENCE_REPOSITORY = (
|
|
|
93
100
|
REFERENCE_JAVA_FILES = 2000
|
|
94
101
|
|
|
95
102
|
#: The other end of the measured range, and the reason this module publishes a
|
|
96
|
-
#: *class* rather than a projected duration (C4-19). Field evaluation #
|
|
103
|
+
#: *class* rather than a projected duration (C4-19). Field evaluation #16, on
|
|
97
104
|
#: Windows 11 / PowerShell 5.1 / pipx / Python 3.10: `spring-audit` on a
|
|
98
|
-
#: 3 342-file repository ran **
|
|
99
|
-
#: the 2 000-file reference
|
|
100
|
-
#:
|
|
101
|
-
#:
|
|
102
|
-
#: be said honestly is the class, the two
|
|
105
|
+
#: 3 342-file repository ran **2 351 s** — 267× the 8.8 s the same command takes
|
|
106
|
+
#: on the 2 000-file reference, for a repository 1,7× the size. Cost does not
|
|
107
|
+
#: scale with file count in any way this product has measured, so a projected
|
|
108
|
+
#: "~15 s on your repository" would be a confident falsehood of exactly the kind
|
|
109
|
+
#: the ledger exists to prevent. What can be said honestly is the class, the two
|
|
110
|
+
#: measured anchors, and how to run it.
|
|
111
|
+
#:
|
|
112
|
+
#: C3-72 replaced evaluation #13's 408 s with #16's 2 351 s here: the same command
|
|
113
|
+
#: on the same repository at the same commit, measured three releases later
|
|
114
|
+
#: (2 557 s in 4.10.6, 2 398 s in 4.10.7, 2 351 s in 4.11.0). The older figure was
|
|
115
|
+
#: the one a budget message quoted as "~600s" for three releases while the
|
|
116
|
+
#: measurement stood four times higher.
|
|
103
117
|
FIELD_ANCHOR = (
|
|
104
|
-
"field evaluation #
|
|
105
|
-
"(Windows, pipx,
|
|
118
|
+
"field evaluation #16: spring-audit on 3 342 Java files took 2 351 s "
|
|
119
|
+
"(Windows, pipx, warm cache) against 8.8 s on the 2 000-file reference"
|
|
106
120
|
)
|
|
107
121
|
FIELD_ANCHOR_JAVA_FILES = 3342
|
|
108
|
-
FIELD_ANCHOR_SECONDS =
|
|
122
|
+
FIELD_ANCHOR_SECONDS = 2351.4
|
|
109
123
|
|
|
110
124
|
|
|
111
125
|
#: Every layer keys on `cache.worktree_signature` — the exact tree state (C1-9).
|
|
@@ -176,14 +190,15 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
176
190
|
"Resolves the conditional bean graph on every run, over the shared CIR a warm "
|
|
177
191
|
"builds — the parse it used to repeat for itself. `--diff` compares two profile "
|
|
178
192
|
"sets over that one IR, so the second side costs the resolution only.",
|
|
179
|
-
"10.1 s → 1.6 s", analysis_class="repo-wide", cold_seconds=10.1, warm_seconds=1.6,
|
|
193
|
+
"10.1 s → 1.6 s", analysis_class="repo-wide", cold_seconds=10.1, warm_seconds=1.6, field_seconds=38.0),
|
|
180
194
|
CommandCache("risk", ("cir", "parse"), "shared", False,
|
|
181
195
|
"Composes what the audit, impact-chain and the posture already answer, so it "
|
|
182
196
|
"pays each of their costs once over the shared CIR a warm builds — one parse "
|
|
183
197
|
"for the whole composition, and the reachability query is cached per symbol "
|
|
184
198
|
"within the run.",
|
|
185
199
|
"not measured on the battery yet — the composition is bounded by the "
|
|
186
|
-
"`spring-audit` + `impact-chain` costs listed here, not by new analysis",
|
|
200
|
+
"`spring-audit` + `impact-chain` costs listed here, not by new analysis",
|
|
201
|
+
analysis_class="deep", field_seconds=3232.2),
|
|
187
202
|
CommandCache("enrich", ("cir", "parse"), "shared", False,
|
|
188
203
|
"Runs the same composition as `risk` over the repository, then joins a SARIF "
|
|
189
204
|
"log to it. Reading the log is negligible; everything a warm helps with is the "
|
|
@@ -213,14 +228,14 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
213
228
|
"Recomputes the endpoint surface on every run, over a parse a warm has already "
|
|
214
229
|
"paid for. Until 3.7.0 the extractor parsed every file itself instead of reading "
|
|
215
230
|
"the shared parse cache, and a warm measurably bought it nothing (C3-6).",
|
|
216
|
-
"3.3 s → 1.4 s (re-measured on 3.7.0; was 2.8 s → 2.9 s)", analysis_class="repo-wide", cold_seconds=3.3, warm_seconds=1.4, field_seconds=
|
|
231
|
+
"3.3 s → 1.4 s (re-measured on 3.7.0; was 2.8 s → 2.9 s)", analysis_class="repo-wide", cold_seconds=3.3, warm_seconds=1.4, field_seconds=5.0),
|
|
217
232
|
CommandCache("spring-audit", ("ris", "parse"), "shared", False,
|
|
218
233
|
"Recomputes every run, but over a parse a warm has already paid for.",
|
|
219
|
-
"8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7, field_seconds=
|
|
234
|
+
"8.8 s → 3.7 s", analysis_class="repo-wide", cold_seconds=8.8, warm_seconds=3.7, field_seconds=2351.4),
|
|
220
235
|
CommandCache("migrate-check", ("cir",), "none", False,
|
|
221
236
|
"Computes its own inventory and shares nothing a warm builds. Only `--blast-radius` "
|
|
222
237
|
"reuses the shared CIR.",
|
|
223
|
-
"4.8 s → 4.8 s", analysis_class="repo-wide", cold_seconds=4.8, warm_seconds=4.8,
|
|
238
|
+
"4.8 s → 4.8 s", analysis_class="repo-wide", cold_seconds=4.8, warm_seconds=4.8, field_seconds=26.7),
|
|
224
239
|
CommandCache("impact-chain", ("cir", "parse"), "shared", False,
|
|
225
240
|
"The CIR is the expensive half — this is where a warm pays most.",
|
|
226
241
|
"9.9 s → 1.7 s", analysis_class="core", cold_seconds=9.9, warm_seconds=1.7),
|
|
@@ -301,9 +316,59 @@ def layer(layer_id: str) -> Layer:
|
|
|
301
316
|
raise KeyError(layer_id)
|
|
302
317
|
|
|
303
318
|
|
|
304
|
-
|
|
319
|
+
@dataclass(frozen=True)
|
|
320
|
+
class Conditioning:
|
|
321
|
+
"""The reader's repository, so the figures below are read against it (C3-60).
|
|
322
|
+
|
|
323
|
+
`verify-edit` is published here as *14,6 s → 9,6 s → 5,6 s* and described as
|
|
324
|
+
*"built for the edit loop"*; on the field repository the same command had not
|
|
325
|
+
answered at 495 s. Both are true, and printing only the first is what made
|
|
326
|
+
the second read as a defect in the reader's setup rather than as the scale
|
|
327
|
+
the figure was never measured at. The numbers do not change — what changes is
|
|
328
|
+
that they arrive with the repository they belong to, and with what is known
|
|
329
|
+
about the one in hand.
|
|
330
|
+
|
|
331
|
+
Built by the caller (`cli`) from `execution_plan`, which owns the scope
|
|
332
|
+
measurement; this module stays free of that dependency because
|
|
333
|
+
`execution_plan` already reads *it*.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
scope: str # "3 342 Java files, cache cold"
|
|
337
|
+
transfers: "Optional[bool]" # do the reference figures apply here?
|
|
338
|
+
per_command: "dict[str, str]" # command → how it can be run here
|
|
339
|
+
|
|
340
|
+
def statement(self) -> str:
|
|
341
|
+
"""One sentence about whether the figures below transfer to this scope."""
|
|
342
|
+
if self.transfers is None:
|
|
343
|
+
return (
|
|
344
|
+
f"This repository ({self.scope}) was not measured, so nothing below "
|
|
345
|
+
f"is claimed about it: the figures are the reference repository's."
|
|
346
|
+
)
|
|
347
|
+
if self.transfers:
|
|
348
|
+
return (
|
|
349
|
+
f"This repository ({self.scope}) is within the size the figures "
|
|
350
|
+
f"below were measured at ({REFERENCE_JAVA_FILES} Java files)."
|
|
351
|
+
)
|
|
352
|
+
return (
|
|
353
|
+
f"This repository ({self.scope}) is larger than the repository the "
|
|
354
|
+
f"figures below were measured on ({REFERENCE_JAVA_FILES} Java files), "
|
|
355
|
+
f"and cost has not been observed to scale with file count: "
|
|
356
|
+
f"{FIELD_ANCHOR}. Read each figure as the reference's, not as yours; "
|
|
357
|
+
f"the `here:` lines say how each command can be run on this one."
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def as_dict(here: "Optional[Conditioning]" = None) -> dict:
|
|
305
362
|
"""The whole model as data — what `ask cache model --json` emits."""
|
|
363
|
+
conditioned: dict = {}
|
|
364
|
+
if here is not None:
|
|
365
|
+
conditioned = {
|
|
366
|
+
"scope_measured": here.scope,
|
|
367
|
+
"reference_figures_transfer": here.transfers,
|
|
368
|
+
"conditioning": here.statement(),
|
|
369
|
+
}
|
|
306
370
|
return {
|
|
371
|
+
**conditioned,
|
|
307
372
|
"invalidation": (
|
|
308
373
|
"Every layer keys on the exact tree state: any change to the analysed files "
|
|
309
374
|
"invalidates it, committed or not. A clean tree keys on the commit, so a repeat "
|
|
@@ -334,6 +399,13 @@ def as_dict() -> dict:
|
|
|
334
399
|
"note": cmd.note,
|
|
335
400
|
"measured": cmd.measured or None,
|
|
336
401
|
"analysis_class": cmd.analysis_class,
|
|
402
|
+
# How this command can be run on the repository in hand, when one
|
|
403
|
+
# was measured. Absent rather than guessed otherwise (C3-60).
|
|
404
|
+
**(
|
|
405
|
+
{"here": here.per_command[cmd.command]}
|
|
406
|
+
if here is not None and cmd.command in here.per_command
|
|
407
|
+
else {}
|
|
408
|
+
),
|
|
337
409
|
"reference_seconds": (
|
|
338
410
|
None
|
|
339
411
|
if cmd.cold_seconds is None
|
|
@@ -366,7 +438,7 @@ def render_markdown() -> str:
|
|
|
366
438
|
return "\n".join(out)
|
|
367
439
|
|
|
368
440
|
|
|
369
|
-
def render_summary() -> str:
|
|
441
|
+
def render_summary(here: "Optional[Conditioning]" = None) -> str:
|
|
370
442
|
"""The model as `ask cache model` prints it by default (C3-63).
|
|
371
443
|
|
|
372
444
|
One line per layer and one line per command — every command named (nothing
|
|
@@ -376,6 +448,9 @@ def render_summary() -> str:
|
|
|
376
448
|
own selling argument is token economy. That detail is still one flag away.
|
|
377
449
|
"""
|
|
378
450
|
lines: list[str] = []
|
|
451
|
+
if here is not None:
|
|
452
|
+
lines.append(here.statement())
|
|
453
|
+
lines.append("")
|
|
379
454
|
lines.append(
|
|
380
455
|
"Invalidation: every layer keys on the exact tree state — any change to the "
|
|
381
456
|
"analysed files invalidates it, committed or not."
|
|
@@ -396,6 +471,8 @@ def render_summary() -> str:
|
|
|
396
471
|
f" {cmd.command.ljust(cmd_width)} {_WARM_LABEL[cmd.warm].ljust(warm_width)} "
|
|
397
472
|
f"({repeat}) [{layers}]"
|
|
398
473
|
)
|
|
474
|
+
if here is not None and cmd.command in here.per_command:
|
|
475
|
+
lines.append(f" {' ' * cmd_width} here: {here.per_command[cmd.command]}")
|
|
399
476
|
lines.append("")
|
|
400
477
|
lines.append(
|
|
401
478
|
"Full detail (measured timings, per-command notes): ask cache model --full"
|
|
@@ -404,11 +481,14 @@ def render_summary() -> str:
|
|
|
404
481
|
return "\n".join(lines)
|
|
405
482
|
|
|
406
483
|
|
|
407
|
-
def render_text() -> str:
|
|
484
|
+
def render_text(here: "Optional[Conditioning]" = None) -> str:
|
|
408
485
|
"""The model in full: every layer and command with its measured timing and note.
|
|
409
486
|
|
|
410
487
|
`ask cache model --full`. See `render_summary` for the default."""
|
|
411
488
|
lines: list[str] = []
|
|
489
|
+
if here is not None:
|
|
490
|
+
lines.append(here.statement())
|
|
491
|
+
lines.append("")
|
|
412
492
|
lines.append("Invalidation — one rule for every layer")
|
|
413
493
|
lines.append(" Every layer keys on the exact tree state. Any change to the analysed files")
|
|
414
494
|
lines.append(" invalidates it, committed or not, tracked or not. A clean tree keys on the")
|
|
@@ -432,4 +512,6 @@ def render_text() -> str:
|
|
|
432
512
|
lines.append(f" {' ' * width} measured: {cmd.measured}")
|
|
433
513
|
if cmd.note:
|
|
434
514
|
lines.append(f" {' ' * width} {cmd.note}")
|
|
515
|
+
if here is not None and cmd.command in here.per_command:
|
|
516
|
+
lines.append(f" {' ' * width} here: {here.per_command[cmd.command]}")
|
|
435
517
|
return "\n".join(lines)
|