sourcecode 3.4.0__tar.gz → 3.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-3.4.0 → sourcecode-3.5.0}/CHANGELOG.md +261 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/PKG-INFO +4 -4
- {sourcecode-3.4.0 → sourcecode-3.5.0}/README.md +3 -3
- {sourcecode-3.4.0 → sourcecode-3.5.0}/pyproject.toml +1 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/archetype.py +4 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/cache_model.py +5 -4
- sourcecode-3.5.0/src/sourcecode/caller_metrics.py +168 -0
- sourcecode-3.5.0/src/sourcecode/caller_reach.py +205 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/cli.py +223 -267
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/compare.py +6 -2
- sourcecode-3.5.0/src/sourcecode/degradation.py +373 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/explain.py +40 -14
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/facts/registry.json +32 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/orchestrator.py +3 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/registry.py +3 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/server.py +4 -2
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/migrate_check.py +86 -12
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/migration_blast.py +5 -3
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/output_budget.py +11 -0
- sourcecode-3.5.0/src/sourcecode/path_admission.py +153 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/posture.py +119 -27
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/pr_comment_renderer.py +6 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/readiness_timeline.py +16 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/repository_ir.py +96 -48
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps.py +9 -1
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/ris.py +11 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/serializer.py +10 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_impact.py +29 -163
- sourcecode-3.4.0/src/sourcecode/caller_metrics.py +0 -61
- {sourcecode-3.4.0 → sourcecode-3.5.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/.gitignore +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/.ruff.toml +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/CONTRIBUTING.md +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/LICENSE +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/SECURITY.md +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/raw +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/scripts/perf_harness.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/license.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/functions/README.md +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-3.4.0 → sourcecode-3.5.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,6 +1,266 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [3.5.0] — 2026-07-31
|
|
4
|
+
|
|
5
|
+
**M7.7 "Robustness floor" — the axis field evaluation #5 scored 4/10.** Ten pieces, and the
|
|
6
|
+
pattern in eight of them is the same: the reported symptom was real, and the measured cause was
|
|
7
|
+
not the one anyone had assumed. Input admission had four behaviours for one fact, not one broken
|
|
8
|
+
command. `posture`'s cost was not the Java parse but a quadratic scan of two branches that could
|
|
9
|
+
not fire — and that dead code was also a false zero on the security axis. The blast radius
|
|
10
|
+
disagreed with itself because there were two traversals of the reverse graph, not because reach
|
|
11
|
+
is hard. Two reported symptoms did not reproduce at all and are recorded as measured rather than
|
|
12
|
+
fixed blind.
|
|
13
|
+
|
|
14
|
+
**Minor, not patch**, on two counts: `impact` now publishes endpoints, transaction boundaries and
|
|
15
|
+
a security surface where it published `0`, so its risk score moves and a pipeline gating on it can
|
|
16
|
+
change colour; and `estimated_effort_days` is nullable where a range applies.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`retrieve` moves from `experimental` to `parked`** (C3-24) — kept working, no longer
|
|
21
|
+
developed, and saying so: `[PARKED]` in its own help, in the README table and in the user
|
|
22
|
+
guide, all rendered from `cli.COMMAND_TIERS`. The battery now also asserts that a parked
|
|
23
|
+
command declares itself, the twin of the assertion that already existed for experimental.
|
|
24
|
+
**Two of the three reported symptoms did not reproduce**, and are recorded that way rather
|
|
25
|
+
than repeated: `transaction-propagation` returns one entity because the battery repository
|
|
26
|
+
has exactly one non-default boundary (`REQUIRES_NEW: 1` of 161) — `observations.total` is the
|
|
27
|
+
tx-index population standing beside the answer, not a truncated total, and a `--limit` exists
|
|
28
|
+
(default unbounded) that records `entities_truncated` when it bites; `security-surface`
|
|
29
|
+
returns 0 where `spring-audit --scope security` also returns 0. What does reproduce is an
|
|
30
|
+
`explanation` block shaped for a ranking that never ran. The surface is not non-functional —
|
|
31
|
+
it is undifferentiated, re-stating behind 15+ intents what the core commands answer better.
|
|
32
|
+
|
|
33
|
+
### Measured
|
|
34
|
+
|
|
35
|
+
- **`review-pr`'s path argument is not decorative — the reported scope defect does not
|
|
36
|
+
reproduce** (C3-27, scope half). Field evaluation #5 reported that given a submodule path the
|
|
37
|
+
command reports the *enclosing* repository as `repo_root` and reviews 227 files from other
|
|
38
|
+
modules. Re-measured on six shapes: a module subdirectory, that same subdirectory as the
|
|
39
|
+
working directory, a nested path given relative to the root, an embedded repository, a **real
|
|
40
|
+
git submodule** (where `.git` is a gitlink *file*, so an `exists()` test is true for it and the
|
|
41
|
+
walk up has to stop there), and a submodule asked for a ref only its parent has. Every run is
|
|
42
|
+
bounded to the path it was given: on BroadleafCommerce the scoped runs return exactly
|
|
43
|
+
`git diff --name-only <ref> -- <scope>` — 5 of 17 files at `core/`, 855 at
|
|
44
|
+
`core/broadleaf-framework/`, 1 222 at `core/` over a 3 332-file range — `_meta.scope` names the
|
|
45
|
+
tree answered about, no sibling-module path appears anywhere in the payload, and the
|
|
46
|
+
missing-ref case is refused (`git_ref_not_found`, exit 1) instead of borrowed from the parent.
|
|
47
|
+
Recorded as not reproducing rather than fixed blind, and the six shapes are now asserted in
|
|
48
|
+
`tests/test_git_scope_namespace.py` so the symptom cannot return silently. The probable closer
|
|
49
|
+
is the Bug A scope-namespace seam, which landed between the evaluated build and this
|
|
50
|
+
measurement.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **`impact` reported `endpoints_affected_count: 0` for a symbol `impact-chain` reached 21
|
|
55
|
+
endpoints from** (C1-16, endpoint half — the caller half closed in 3.5.0). A false zero on the
|
|
56
|
+
blast-radius axis, which is the number a user takes a risk decision with. The cause was
|
|
57
|
+
measured, not guessed: there were **two traversals** of the reverse graph, and they disagreed
|
|
58
|
+
twice.
|
|
59
|
+
- `impact` looked up the exact reverse-graph key. The callers of a class are stored under its
|
|
60
|
+
**method** keys (`Foo#doWork`), never under `Foo`, so the walk terminated at every
|
|
61
|
+
class-level node — which is exactly what a DI `injects` edge normalises to. 125 indirect
|
|
62
|
+
callers instead of 1 442.
|
|
63
|
+
- It admitted `implements`/`extends` as caller edges (an implementor does not call an
|
|
64
|
+
interface by implementing it, CH-006) under a comment claiming its edge set was *"consistent
|
|
65
|
+
with `spring_impact._SKIP_EDGE_TYPES`"*, which skips both. And it held no interface index, so
|
|
66
|
+
it never crossed the DI boundary at all: on `Money` it reached 200 classes and **zero
|
|
67
|
+
controllers**, against 441 and 18.
|
|
68
|
+
- New authority `sourcecode/caller_reach.py`, registered as fact `blast_radius_reach`, with
|
|
69
|
+
`impact`, `impact-chain` and `migrate-check --blast-radius` all bound to it. On
|
|
70
|
+
BroadleafCommerce's `Money`, `impact` now reports **20 of the 21 endpoints** (0 before),
|
|
71
|
+
**53 transactional boundaries** (0), **20 persistence paths** (2) and **15 security-gated
|
|
72
|
+
endpoints** (0) — while `stats.direct_caller_count` stays **134**, so the caller axis closed
|
|
73
|
+
in 3.5.0 does not move. Parity verified command-to-command on spring-petclinic
|
|
74
|
+
(`OwnerRepository`: 13 = 13 endpoints, exact) and open-banking-gateway (two symbols, exact):
|
|
75
|
+
the reconciliation adds no false positives. Cost unchanged (5.0 s vs 4.9 s on that repo).
|
|
76
|
+
- Also fixed here: the explanation prose read *"433 direct callers"* beside a payload
|
|
77
|
+
publishing 134 caller classes and 433 reference sites, each named for its unit. Prose is
|
|
78
|
+
what a reader quotes, so it now names its unit too.
|
|
79
|
+
- **Residual, stated because it is measured:** one endpoint of the 21
|
|
80
|
+
(`BroadleafOauthRegisterController#processRegister`) is still reached by `impact-chain` and
|
|
81
|
+
not by `impact`, because the two commands read *different graphs* — `impact` builds the repo
|
|
82
|
+
IR, `impact-chain` the CIR, and the CIR carries a method key for that handler the repo IR
|
|
83
|
+
does not. That is a graph-population gap, not a traversal one. Seed expansion is asymmetric
|
|
84
|
+
for the same reason (`impact-chain` expands a seed to its subtypes, `impact` only to the
|
|
85
|
+
interfaces it implements).
|
|
86
|
+
|
|
87
|
+
- **`posture` costs a sixth of what it did, and a warm is finally worth something to it**
|
|
88
|
+
(C3-25) — 24.5 s → **10.1 s** cold and 19.3 s → **1.6 s** after `cache warm`, measured on
|
|
89
|
+
BroadleafCommerce in isolation. Field evaluation #5 measured 195 s, ×18 the session median,
|
|
90
|
+
on the one command the product differentiates on, and the cache model honestly documented
|
|
91
|
+
that a warm bought it almost nothing. Both had the same cause, and it was not the Java parse
|
|
92
|
+
everyone blamed:
|
|
93
|
+
- **The security predicate re-walked every node in the repository for every class** —
|
|
94
|
+
6 075 × ~29 000 ≈ 176 M dictionary reads on the battery repository, larger than the parse
|
|
95
|
+
itself — looking for `node["return_type"]` and `node["supertypes"]`. **The IR publishes
|
|
96
|
+
neither key on a node.** Inheritance and return types are `extends`, `implements` and
|
|
97
|
+
`returns` *edges*, so both branches were structurally dead in every release: a repository
|
|
98
|
+
that wires its chain with a `@Bean SecurityFilterChain` or a filter subclass and no
|
|
99
|
+
`@EnableWebSecurity` was reported as configuring no security at all — a **false zero on the
|
|
100
|
+
axis this command exists for**. The predicate now reads the edges, once per IR. On
|
|
101
|
+
BroadleafCommerce it recovers 6 request-chain configurations, verified 6/6 against source
|
|
102
|
+
with no false positive, one of which is `@ConditionalOnProperty` and now correctly qualifies
|
|
103
|
+
the endpoint claim as unresolved instead of silently strengthening it.
|
|
104
|
+
- **`posture` built its own CIR on every run** — the same parse `cache warm`, `explain` and
|
|
105
|
+
`impact-chain` already share. It now goes through the shared knowledge cache, poison-safe by
|
|
106
|
+
the `peek_cir` rule: the knowledge key is repo-wide and carries no scope, so a run bounded to
|
|
107
|
+
a subdirectory neither reads nor writes it and keeps its own unshared build.
|
|
108
|
+
- **`--diff` parsed the repository once per side.** The IR does not depend on which profiles
|
|
109
|
+
are asked about — only the resolution over it does — so the second side now costs the
|
|
110
|
+
resolution alone: 2.3 s warm for a two-set comparison, the shape a per-PR gate runs.
|
|
111
|
+
|
|
112
|
+
- **A degraded run now states the effect of the degradation** — invariant **I-8**, accepted and
|
|
113
|
+
enforced (C3-27, and the class field evaluation #5 named). `review-pr` published
|
|
114
|
+
`analysis_limiter: {"missing_signals": ["dependency_graph", "import_graph"]}`: what was
|
|
115
|
+
absent, and nothing about what its absence changed. A reader could not tell whether the answer
|
|
116
|
+
was a floor, a ceiling or unaffected — the same defect shape as `statically_unreferenced: 0`,
|
|
117
|
+
one level up.
|
|
118
|
+
- Every signal a command can report as missing now carries **what it feeds**, **what the
|
|
119
|
+
answer does without it**, and a **`direction`**: `under_reports` (read the answer as a
|
|
120
|
+
floor — absence of a finding is not evidence of none), `over_reports` (a ceiling), or
|
|
121
|
+
`undetermined`. Mixed directions collapse to `undetermined`: an answer that is a floor in
|
|
122
|
+
one place and a ceiling in another is neither. The block ends with one sentence on how to
|
|
123
|
+
read the result, and the GitHub comment prints it beside the `OMITTED` badge — a reviewer
|
|
124
|
+
reads the comment, not the JSON.
|
|
125
|
+
- One authority (`sourcecode/degradation.py`) serves every emitter, and the battery discovers
|
|
126
|
+
signal names **from the source by AST**, so a signal added without an effect statement fails
|
|
127
|
+
the suite. That scan found a second emitter the field never reached: **`archetype`** declares
|
|
128
|
+
`repo_ir_subsystem_masses` and `semantic_ir_spi_fanin` missing on *every* run — signals wired
|
|
129
|
+
into no release — and its missing `endpoints` signal enters the score as `0.0` rather than as
|
|
130
|
+
unknown, so an HTTP-facing repository is scored as if it exposed no HTTP surface. Both now
|
|
131
|
+
say so in the payload; correcting the score itself is separate work.
|
|
132
|
+
- **The second family is now covered too: what the product cuts on purpose.** A missing signal
|
|
133
|
+
is something the run did not have; a **cap** is something it had and chose not to publish.
|
|
134
|
+
Those said only *that* they capped — `"_truncation_summary": {"total_omitted_items": 1442}`,
|
|
135
|
+
`"bfs_truncation_reason": "hub_class_depth_cap"`, `"is_stale": true` — and a count of omitted
|
|
136
|
+
items is not an effect. `CAP_EFFECTS` gives each one the same three fields plus a fourth,
|
|
137
|
+
`exact`, which is what separates a cap from a missing signal: what survived the cut.
|
|
138
|
+
- **The output budget** publishes the least intuitive effect of all: *nothing moved*. Every
|
|
139
|
+
figure is computed before serialisation, so the counts stay exact and only the lists became
|
|
140
|
+
samples — and deriving a count from a truncated list is exactly how the field arrived at
|
|
141
|
+
four contradictory fan-in figures. The block now says so, and the battery asserts a trimmed
|
|
142
|
+
payload keeps its count.
|
|
143
|
+
- **Display caps** (`impact-chain --limit`, `impact`'s 30-class sample, `retrieve --limit`)
|
|
144
|
+
state that the total beside the list was computed over the whole population, so list and
|
|
145
|
+
total disagreeing is construction, not contradiction.
|
|
146
|
+
- **The hub-class depth cap** names every figure it turns into a floor — indirect callers and
|
|
147
|
+
the endpoints, transaction boundaries, security surface and risk score derived from them —
|
|
148
|
+
and every figure it leaves exact. A zero under a cap means the capped walk found none.
|
|
149
|
+
- **A stale snapshot** is `undetermined`, not a floor: it describes a different tree, so a
|
|
150
|
+
symbol added since is missing *and* a symbol deleted since is still listed.
|
|
151
|
+
- **An unavailable section** (`migrate-check --blast-radius` when the IR cannot be built)
|
|
152
|
+
says the failure is bounded to that block and the findings beside it are untouched.
|
|
153
|
+
- Same enforcement as the signal family: the battery discovers cap names from the source by
|
|
154
|
+
AST, so a cap applied without an effect statement fails the suite.
|
|
155
|
+
|
|
156
|
+
- **A run that succeeded now writes nothing to stderr** (C3-26). PowerShell 5.1 — a declared
|
|
157
|
+
target platform — turns any stderr write from a native command into a `NativeCommandError`,
|
|
158
|
+
so an informational line makes a successful run look like a failed one and forces the caller
|
|
159
|
+
to swallow errors it should be reporting. Measured non-TTY before fixing: `migrate-check`
|
|
160
|
+
wrote a size advisory, `validation` a note about an empty result, `explain` a cache hit/miss
|
|
161
|
+
line; everything else was already clean. Those three now go through one seam (`cli._notice`)
|
|
162
|
+
that speaks only when stderr is a terminal. No `--quiet` flag: a flag has to be remembered by
|
|
163
|
+
every caller, on the platform where forgetting it is fatal.
|
|
164
|
+
- **`cache warm` and `cache clear` print their status on stdout**, where `cache status` in the
|
|
165
|
+
same group already printed it. Their status *is* their answer — on stderr, `ask cache warm >
|
|
166
|
+
log.txt` captured nothing at all.
|
|
167
|
+
- Errors keep stderr, where they pair with a non-zero exit; `tests/test_stderr_purity_matrix.py`
|
|
168
|
+
asserts both halves across 13 commands through a real subprocess, because a CliRunner blurs
|
|
169
|
+
the two streams — which is how three of these survived the stdout-purity battery that
|
|
170
|
+
already existed.
|
|
171
|
+
|
|
172
|
+
- **Every example the CLI prints now parses against the CLI that prints it** (C3-16). The root
|
|
173
|
+
help led its gating panel with `pr-impact . --since main`; `pr-impact` has no `--since`. It
|
|
174
|
+
was reported by three field evaluations and survived all three, because the check was a
|
|
175
|
+
person re-reading a hand-written block — and the header panels print invocations *without*
|
|
176
|
+
the `ask ` prefix, so even grepping for `ask ` missed it. The example is now
|
|
177
|
+
`pr-impact . --files -` (the stdin form, verified against a real `git diff --name-only`), and
|
|
178
|
+
`tests/test_help_examples.py` extracts all 156 invocations from every help screen — root
|
|
179
|
+
panels, command docstrings, group and subcommand help — and parses each one against the
|
|
180
|
+
command it names. The battery states its own scope: the command and its options exist and
|
|
181
|
+
take the arity given; values are not validated and nothing is executed.
|
|
182
|
+
|
|
183
|
+
- **One class, four fan-in figures — two of them wrong about their own unit** (C1-16, field
|
|
184
|
+
evaluation #5). On `org.broadleafcommerce.common.money.Money` the product published 1 868,
|
|
185
|
+
673, 418 and 151 under names that all read as fan-in, and the note written to pre-empt that
|
|
186
|
+
confusion promised an ordering (`modernize.in_degree` *"always the largest"*, the other two
|
|
187
|
+
within *"a small margin"*) that measurement falsified by 2,8× and 177 %. This is the number
|
|
188
|
+
a risk decision is taken with: *"¿el blast radius son 484 o 611 clases?"*
|
|
189
|
+
- **`impact.stats.direct_caller_count` counted caller *symbols*** — 418, of which 346 were
|
|
190
|
+
method-level, so a class was counted once per method touching the target. It now counts
|
|
191
|
+
**distinct classes** (134) with the reference sites beside it as
|
|
192
|
+
**`direct_caller_symbol_count`** (418). The risk score still weighs reference sites; it was
|
|
193
|
+
the published unit that was false. `direct_callers` is a class list (`direct_caller_symbols`
|
|
194
|
+
keeps the sites), so the array and its count are finally the same unit.
|
|
195
|
+
- **`explain.incoming_callers` deduplicated by simple name**, collapsing 186 distinct classes
|
|
196
|
+
into 151 and dropping 35 real dependents. Identity is the FQN; a colliding simple name is
|
|
197
|
+
rendered qualified. New **`incoming_callers_count`** is measured before the `--limit` cap,
|
|
198
|
+
so a consumer never has to take `len()` of a truncated array.
|
|
199
|
+
- **`impact-chain.metadata.callers_total` was the size of the whole chain**, not fan-in, and
|
|
200
|
+
was absent from the note. It stays (removing a published key is breaking) and now says so,
|
|
201
|
+
beside **`chain_classes_total`** (441) and **`direct_caller_count`** (134) — which equals
|
|
202
|
+
`impact`'s figure by construction. `explain` is a declared superset: it admits import-only
|
|
203
|
+
references, a blast radius does not.
|
|
204
|
+
- **The note is generated from a registry** (`caller_metrics.FAN_IN_FIGURES`), each figure
|
|
205
|
+
carrying its unit and the references it admits, and the battery asserts every figure appears
|
|
206
|
+
with its unit. No ordering between units is promised any more — the three falsified phrases
|
|
207
|
+
are banned by test. Registered as fact `caller_fan_in` (ADR-0008 R11-R14).
|
|
208
|
+
- `compare`'s evidence trail read `len()` of the 30-item display array and told every hub
|
|
209
|
+
class in the repository it had "30 direct callers". It reads `stats` now.
|
|
210
|
+
- Still open and stated rather than guessed: `impact` reports `endpoints_affected_count: 0`
|
|
211
|
+
where `impact-chain` returns 21 endpoints for the same class. Measured today, the two
|
|
212
|
+
traversals reach different populations (259 vs 441 classes at full depth), so that half is
|
|
213
|
+
engine reach, not presentation.
|
|
214
|
+
|
|
215
|
+
- **A command must not answer a question about a repository it could not read** (C3-22, C3-23,
|
|
216
|
+
the robustness finding of field evaluation #5 — *"un gate que puede pasar sin analizar nada
|
|
217
|
+
es peor que no tener gate, porque genera confianza injustificada"*). The same nonexistent
|
|
218
|
+
path produced four different behaviours: six commands refused it correctly, `verify`
|
|
219
|
+
answered `{"verdict": "pass", "exit_code": 0}`, `posture` answered an all-zeros payload with
|
|
220
|
+
exit 0, `archetype` raised a `FileNotFoundError` traceback through the JSON-always contract,
|
|
221
|
+
and `cold-start` reported `no_ris`. In `ask verify $DIR --fail-on new`, one typo in the
|
|
222
|
+
variable was a permanently green pipeline.
|
|
223
|
+
- **One admission authority** (`sourcecode.path_admission`): "can this path be analysed?" is
|
|
224
|
+
one fact, so **25 path-taking commands** now derive it in one place instead of twenty
|
|
225
|
+
inline copies plus four omissions. The refusal carries a machine-readable **`reason`** —
|
|
226
|
+
`missing`, `not_a_directory`, or `unreadable`.
|
|
227
|
+
- **`unreadable` is new and was not in the field report.** A directory that exists but cannot
|
|
228
|
+
be listed produced the same `archetype` traceback; it is now refused before the scan starts.
|
|
229
|
+
- **`verify` and `verify-edit` refuse with exit 2, not 1.** Their published contract reserves
|
|
230
|
+
2 for *unverified* — the run that could not look — as distinct from 1, the run that looked
|
|
231
|
+
and found violations. A gate that never analysed anything reports neither `pass` nor a
|
|
232
|
+
violation count.
|
|
233
|
+
- **`impact-chain` and `impact` name the repository they actually read.** A path in the
|
|
234
|
+
symbol slot made them report `not_found` about the current working directory. A file path
|
|
235
|
+
is still a legitimate symbol; a symbol that *is* a directory, or that carries a path
|
|
236
|
+
separator and does not exist, now says which repository was analysed and how the arguments
|
|
237
|
+
are ordered.
|
|
238
|
+
- The battery discovers path-taking commands from the CLI itself: a new command that skips
|
|
239
|
+
the gate fails the suite.
|
|
240
|
+
|
|
241
|
+
- **An effort figure is a point only when nothing under it is a range** (C1-12, the third
|
|
242
|
+
reconciliation gate of field evaluation #4). `migrate-check` folded the Hibernate 5→6 slice
|
|
243
|
+
into the headline as the **midpoint** of a range the same report labels `confidence: low`,
|
|
244
|
+
and that the Hibernate effort model itself calls a non-deduplicated *upper bound*. Measured
|
|
245
|
+
on the battery: BroadleafCommerce billed **157.7 d, 153.7 of them (97%) from 76.5–230.8**;
|
|
246
|
+
openmrs-core 76.4 d with 62.2 (81%) from 28.9–95.6. The arithmetic was right and the claim
|
|
247
|
+
was still false — a 3× span presented as one number.
|
|
248
|
+
- New top-level **`estimated_effort_range_days`** `{low, high, confidence, basis}`. The band
|
|
249
|
+
is the worst confidence of the contributing slices; findings effort is a per-file
|
|
250
|
+
measurement and enters as a point.
|
|
251
|
+
- **`estimated_effort_days` is `null`** when a range slice is in the total (never `0` — that
|
|
252
|
+
reads as "no work"), and stays a number on every repo where no range applies. The text
|
|
253
|
+
report prints the span: `Estimated effort: 80.6–234.9d (low confidence — not a point
|
|
254
|
+
estimate)`.
|
|
255
|
+
- `effort_breakdown.hibernate_rewrite_effort_days` is `null`: the midpoint is not a
|
|
256
|
+
measurement of anything, so it is no longer published. The range sits beside it in
|
|
257
|
+
`hibernate_rewrite_range`, with `hibernate_rewrite_effort_basis` and `unestimated_slices`.
|
|
258
|
+
- **An inferred version buys no number.** When the 5→6 axis applies only because a Spring
|
|
259
|
+
Boot 2 BOM manages Hibernate 5.x — no version declared in the build — the rewrite *scope*
|
|
260
|
+
is a hypothesis: the slice contributes neither a point nor a range, and the gap is named.
|
|
261
|
+
The readiness dimension stays applicable; not estimating is not pretending it is absent.
|
|
262
|
+
- Readiness snapshots/trends carry `estimated_effort_low_days` / `estimated_effort_high_days`
|
|
263
|
+
so the series does not go blind exactly where the effort is largest.
|
|
4
264
|
|
|
5
265
|
## [3.4.0] — 2026-07-29
|
|
6
266
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -220,8 +220,8 @@ experimental: both are true, and they are two different facts.
|
|
|
220
220
|
|---|---|---|
|
|
221
221
|
| **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
|
|
222
222
|
| **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
|
|
223
|
-
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype`
|
|
224
|
-
| **parked** | kept working, no longer developed |
|
|
223
|
+
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
|
|
224
|
+
| **parked** | kept working, no longer developed | `retrieve` |
|
|
225
225
|
|
|
226
226
|
The same table is printed by `ask --help`, and both are generated from one authority
|
|
227
227
|
(`cli.COMMAND_TIERS`) — the battery fails if a command is in no tier, or if this file drifts
|
|
@@ -252,7 +252,7 @@ from it.
|
|
|
252
252
|
| `plan` / `compare` / `delta` / `contract-diff` | supported | what to review for a change; candidates by measured cost; outcome of a change; public-contract break | no verdicts, measured cost only |
|
|
253
253
|
| `validation` | supported | request-body validation coverage and gaps | |
|
|
254
254
|
| `baseline capture\|diff\|trend` | supported | versioned architectural metrics over time | trend reporting, not gating |
|
|
255
|
-
| `retrieve` |
|
|
255
|
+
| `retrieve` | parked | typed knowledge queries over the model | |
|
|
256
256
|
| `archetype` | experimental | evidence-based architectural archetype | |
|
|
257
257
|
| `rename-class` / `chunk-file` | supported | word-boundary Java rename; split a large file for an agent | |
|
|
258
258
|
| `cache status\|warm\|model\|clear` · `auth` · `telemetry` · `mcp` · `config` · `version` | supported | housekeeping | `activate` too |
|
|
@@ -182,8 +182,8 @@ experimental: both are true, and they are two different facts.
|
|
|
182
182
|
|---|---|---|
|
|
183
183
|
| **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
|
|
184
184
|
| **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
|
|
185
|
-
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype`
|
|
186
|
-
| **parked** | kept working, no longer developed |
|
|
185
|
+
| **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` |
|
|
186
|
+
| **parked** | kept working, no longer developed | `retrieve` |
|
|
187
187
|
|
|
188
188
|
The same table is printed by `ask --help`, and both are generated from one authority
|
|
189
189
|
(`cli.COMMAND_TIERS`) — the battery fails if a command is in no tier, or if this file drifts
|
|
@@ -214,7 +214,7 @@ from it.
|
|
|
214
214
|
| `plan` / `compare` / `delta` / `contract-diff` | supported | what to review for a change; candidates by measured cost; outcome of a change; public-contract break | no verdicts, measured cost only |
|
|
215
215
|
| `validation` | supported | request-body validation coverage and gaps | |
|
|
216
216
|
| `baseline capture\|diff\|trend` | supported | versioned architectural metrics over time | trend reporting, not gating |
|
|
217
|
-
| `retrieve` |
|
|
217
|
+
| `retrieve` | parked | typed knowledge queries over the model | |
|
|
218
218
|
| `archetype` | experimental | evidence-based architectural archetype | |
|
|
219
219
|
| `rename-class` / `chunk-file` | supported | word-boundary Java rename; split a large file for an agent | |
|
|
220
220
|
| `cache status\|warm\|model\|clear` · `auth` · `telemetry` · `mcp` · `config` · `version` | supported | housekeeping | `activate` too |
|
|
@@ -30,6 +30,7 @@ from dataclasses import dataclass, field
|
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
from typing import Any, Optional
|
|
32
32
|
|
|
33
|
+
from sourcecode.degradation import analysis_limiter as _analysis_limiter
|
|
33
34
|
from sourcecode.graph_evidence import GraphEvidence, GraphEvidenceProvider, GraphEvidenceResult
|
|
34
35
|
from sourcecode.schema import SourceMap
|
|
35
36
|
from sourcecode.tree_utils import flatten_file_tree
|
|
@@ -157,6 +158,9 @@ class ArchetypeAnalysis:
|
|
|
157
158
|
},
|
|
158
159
|
"signals_used": self.signals_used,
|
|
159
160
|
"signals_missing": self.signals_missing,
|
|
161
|
+
# I-8: the names alone say nothing about what the classification lost.
|
|
162
|
+
# Same authority the review-pr limiter uses, so both read alike.
|
|
163
|
+
"analysis_limiter": _analysis_limiter(self.signals_missing),
|
|
160
164
|
"generated_from": self.generated_from,
|
|
161
165
|
"graph_metrics": {k: round(v, 4) for k, v in self.graph_metrics.items()},
|
|
162
166
|
}
|
|
@@ -125,10 +125,11 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
125
125
|
"`--env-map`, `--depth N` and `--exclude` change the *analysis*, so they miss the "
|
|
126
126
|
"warmed core and rescan — this is the 171 s the field measured after a 103 s warm.",
|
|
127
127
|
"--compact 17.7 s → 0.3 s; --agent --full --env-map --depth 20 34.7 s → 33.9 s (no gain)"),
|
|
128
|
-
CommandCache("posture", ("parse"
|
|
129
|
-
"Resolves the conditional bean graph on every run
|
|
130
|
-
"
|
|
131
|
-
"
|
|
128
|
+
CommandCache("posture", ("cir", "parse"), "shared", False,
|
|
129
|
+
"Resolves the conditional bean graph on every run, over the shared CIR a warm "
|
|
130
|
+
"builds — the parse it used to repeat for itself. `--diff` compares two profile "
|
|
131
|
+
"sets over that one IR, so the second side costs the resolution only.",
|
|
132
|
+
"10.1 s → 1.6 s"),
|
|
132
133
|
CommandCache("endpoints", ("ris", "parse"), "none", False,
|
|
133
134
|
"Recomputes the endpoint surface on every run and refreshes the RIS endpoint index. "
|
|
134
135
|
"Measured: a warm buys it nothing.",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""The authority for "how many things reference this class" (ADR-0008, R11-R14).
|
|
2
|
+
|
|
3
|
+
Several commands publish such a number. They are not the same number, and the
|
|
4
|
+
gap is not a rounding margin: measured on `org.broadleafcommerce.common.money.Money`
|
|
5
|
+
(BroadleafCommerce, one version, one machine) the product published **four**
|
|
6
|
+
figures — 1 868, 673, 418 and 151 — under names that all read as fan-in. Field
|
|
7
|
+
evaluation #5 asked the only question that matters about that: *"¿el blast radius
|
|
8
|
+
son 484 o 611 clases?"*
|
|
9
|
+
|
|
10
|
+
The previous version of this module promised a relationship in prose:
|
|
11
|
+
`modernize.in_degree >= explain.incoming_callers ~= impact.stats.direct_caller_count`,
|
|
12
|
+
with `in_degree` *"always the largest of the three"* and the other two differing
|
|
13
|
+
*"by a small margin"*. Measurement falsified both halves — `impact-chain` was not
|
|
14
|
+
in the relationship at all and was 2,8× the figure declared always-largest, and
|
|
15
|
+
the *"small margin"* was 177 %. A published relationship is a contract, so this
|
|
16
|
+
module no longer publishes prose: it publishes a **registry of figures**, each
|
|
17
|
+
with its unit and its edge population, and the note is *generated* from it. What
|
|
18
|
+
the battery can assert is what the registry says, which is why it cannot drift
|
|
19
|
+
again.
|
|
20
|
+
|
|
21
|
+
Two of the four figures were also wrong about their own unit, and that is the
|
|
22
|
+
defect underneath the divergence:
|
|
23
|
+
|
|
24
|
+
- `impact.stats.direct_caller_count` counted caller **symbols** — 418, of which
|
|
25
|
+
346 were method-level — while its own note called them distinct classes. The
|
|
26
|
+
same class was counted once per method that touches the target. As classes:
|
|
27
|
+
**134**.
|
|
28
|
+
- `explain.incoming_callers` deduplicated by **simple name**, collapsing 186
|
|
29
|
+
distinct classes into 151 and silently dropping 35 of them (the identity rule
|
|
30
|
+
that made `by_controller` keys FQNs applies here too).
|
|
31
|
+
|
|
32
|
+
With both fixed, the residual difference is a *declared* one: `impact` excludes
|
|
33
|
+
`imports` edges from a blast radius (an import is not a runtime dependency,
|
|
34
|
+
FP-001) and `explain` includes them, so `explain` counts a superset. That is a
|
|
35
|
+
fact about populations, stated per figure, not a promise about magnitudes.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Iterable
|
|
40
|
+
|
|
41
|
+
from sourcecode.fqn_utils import normalize_owner_fqn
|
|
42
|
+
|
|
43
|
+
#: Unit tags. Two figures are comparable only if both agree.
|
|
44
|
+
UNIT_CLASSES = "distinct_classes"
|
|
45
|
+
UNIT_SYMBOLS = "caller_symbols"
|
|
46
|
+
UNIT_EDGES = "graph_edges"
|
|
47
|
+
|
|
48
|
+
#: Population tags — *which* references were admitted, not how they were counted.
|
|
49
|
+
POP_REFERENCES_WITH_IMPORTS = "references_including_imports"
|
|
50
|
+
POP_REFERENCES_NO_IMPORTS = "references_excluding_imports"
|
|
51
|
+
POP_ALL_EDGES = "all_incoming_edges"
|
|
52
|
+
POP_TRANSITIVE_CHAIN = "transitive_chain"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def caller_classes(symbols: Iterable[str], *, exclude: Iterable[str] = ()) -> set[str]:
|
|
56
|
+
"""The distinct classes behind a set of caller symbols — the fan-in unit.
|
|
57
|
+
|
|
58
|
+
A caller list mixes granularities: `pkg.Utils`, `pkg.Utils#getMoney`,
|
|
59
|
+
`pkg.Utils.field`. Counting it raw answers "how many references" and calls
|
|
60
|
+
it "how many classes". This is the one place that normalisation happens.
|
|
61
|
+
"""
|
|
62
|
+
excluded = {normalize_owner_fqn(e) for e in exclude}
|
|
63
|
+
out: set[str] = set()
|
|
64
|
+
for sym in symbols:
|
|
65
|
+
if not sym:
|
|
66
|
+
continue
|
|
67
|
+
owner = normalize_owner_fqn(sym)
|
|
68
|
+
if owner and owner not in excluded:
|
|
69
|
+
out.add(owner)
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def caller_class_count(symbols: Iterable[str], *, exclude: Iterable[str] = ()) -> int:
|
|
74
|
+
"""``len(caller_classes(...))`` — the number a fan-in figure may publish."""
|
|
75
|
+
return len(caller_classes(symbols, exclude=exclude))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#: Every published fan-in figure, with what it counts. A figure that is not here
|
|
79
|
+
#: is not allowed to be emitted (asserted by `tests/test_fan_in_authority.py`).
|
|
80
|
+
FAN_IN_FIGURES: tuple[dict[str, str], ...] = (
|
|
81
|
+
{
|
|
82
|
+
"key": "explain.incoming_callers_count",
|
|
83
|
+
"unit": UNIT_CLASSES,
|
|
84
|
+
"population": POP_REFERENCES_WITH_IMPORTS,
|
|
85
|
+
"means": "distinct classes that reference this class, imports included",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"key": "impact.stats.direct_caller_count",
|
|
89
|
+
"unit": UNIT_CLASSES,
|
|
90
|
+
"population": POP_REFERENCES_NO_IMPORTS,
|
|
91
|
+
"means": "distinct classes that call or depend on this class; import-only "
|
|
92
|
+
"references are excluded from a blast radius",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"key": "impact.stats.direct_caller_symbol_count",
|
|
96
|
+
"unit": UNIT_SYMBOLS,
|
|
97
|
+
"population": POP_REFERENCES_NO_IMPORTS,
|
|
98
|
+
"means": "the same population counted as reference sites (methods and "
|
|
99
|
+
"fields), which is what the risk score weighs",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"key": "impact-chain.metadata.chain_classes_total",
|
|
103
|
+
"unit": UNIT_CLASSES,
|
|
104
|
+
"population": POP_TRANSITIVE_CHAIN,
|
|
105
|
+
"means": "every class in the call chain, direct AND transitive — a reach "
|
|
106
|
+
"figure, not fan-in; unbounded above by any direct-caller count",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"key": "impact-chain.metadata.direct_caller_count",
|
|
110
|
+
"unit": UNIT_CLASSES,
|
|
111
|
+
"population": POP_REFERENCES_NO_IMPORTS,
|
|
112
|
+
"means": "distinct classes calling the target at depth 1",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"key": "modernize.in_degree",
|
|
116
|
+
"unit": UNIT_EDGES,
|
|
117
|
+
"population": POP_ALL_EDGES,
|
|
118
|
+
"means": "incoming graph edges at symbol level, not deduplicated to "
|
|
119
|
+
"classes — a ranking weight, never a count of classes",
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _render_reconciliation() -> str:
|
|
125
|
+
lines = [
|
|
126
|
+
"Fan-in figures differ across commands because they count different "
|
|
127
|
+
"things. Each figure below states its unit and the references it admits; "
|
|
128
|
+
"two figures are comparable only when both match. No ordering between "
|
|
129
|
+
"units is promised — an earlier version of this note promised one and "
|
|
130
|
+
"measurement falsified it."
|
|
131
|
+
]
|
|
132
|
+
for fig in FAN_IN_FIGURES:
|
|
133
|
+
lines.append(f"{fig['key']} [{fig['unit']}] = {fig['means']}.")
|
|
134
|
+
lines.append(
|
|
135
|
+
"Any list of callers in the payload may be truncated for output size and "
|
|
136
|
+
"is a display sample: read the *_count fields, never len() of the array."
|
|
137
|
+
)
|
|
138
|
+
return " ".join(lines)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
CALLER_METRIC_RECONCILIATION: str = _render_reconciliation()
|
|
142
|
+
|
|
143
|
+
#: Kept for the payload key that was `callers_total` before it was named for what
|
|
144
|
+
#: it counts. Consumers reading the old key get the same number and this note.
|
|
145
|
+
CALLERS_TOTAL_DEPRECATION: str = (
|
|
146
|
+
"metadata.callers_total is the size of the whole call chain (direct + "
|
|
147
|
+
"transitive), not a fan-in count; it is superseded by "
|
|
148
|
+
"metadata.chain_classes_total and metadata.direct_caller_count."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Cycle / tangle metrics come from TWO different graphs. Both are correct; they
|
|
153
|
+
# answer different questions, and without saying so the pair reads as one command
|
|
154
|
+
# contradicting another (external audit 2026-07-25: modernize reported 15 cyclic
|
|
155
|
+
# tangles while retrieve subsystem-coupling reported import_cycles: 0).
|
|
156
|
+
CYCLE_METRIC_RECONCILIATION: str = (
|
|
157
|
+
"Cycle metrics differ across commands BY DESIGN. modernize.cross_module_tangles "
|
|
158
|
+
"is computed over the SYMBOL dependency graph, aggregated to subsystem packages: "
|
|
159
|
+
"coupling_type=cyclic means two subsystems reference each other (a 2-cycle "
|
|
160
|
+
"between package groups). The module-graph metrics (import_cycles, cyclic_density, "
|
|
161
|
+
"scc_mass — used by archetype and the subsystem-coupling retrieval intent) are "
|
|
162
|
+
"computed over the MODULE/DIRECTORY graph, are bounded for cost (node/edge caps), "
|
|
163
|
+
"and count strongly-connected components among modules. A repo can therefore "
|
|
164
|
+
"show mutual package-level coupling (tangles > 0) with no module-level SCC "
|
|
165
|
+
"(import_cycles = 0): the coupling exists between package groups inside the same "
|
|
166
|
+
"module. Neither number invalidates the other — read tangles for decomposition "
|
|
167
|
+
"risk and import_cycles for module-graph topology."
|
|
168
|
+
)
|