sourcecode 3.2.2__tar.gz → 3.4.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.2.2 → sourcecode-3.4.0}/CHANGELOG.md +103 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/PKG-INFO +8 -5
- {sourcecode-3.2.2 → sourcecode-3.4.0}/README.md +7 -4
- {sourcecode-3.2.2 → sourcecode-3.4.0}/pyproject.toml +1 -1
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/cli.py +41 -39
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/license.py +68 -6
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/migrate_check.py +89 -7
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/verify_edit.py +190 -7
- {sourcecode-3.2.2 → sourcecode-3.4.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/.gitignore +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/.ruff.toml +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/CONTRIBUTING.md +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/LICENSE +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/SECURITY.md +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/raw +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/scripts/perf_harness.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/archetype.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/compare.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/envelope.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/perf.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/posture.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/functions/README.md +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-3.2.2 → sourcecode-3.4.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,109 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [3.4.0] — 2026-07-29
|
|
6
|
+
|
|
7
|
+
**Two of the five reconciliation gates, and one commercial surface that answered five ways.**
|
|
8
|
+
The theme of field evaluation #4 was not missing analysis: it was analysis the product had
|
|
9
|
+
already done and did not consult — a migration estimate that ignored its own
|
|
10
|
+
`auto_fix_available`, and a gate that reported `pass` for a file it never looked at.
|
|
11
|
+
|
|
12
|
+
**A minor, not a patch, and for one reason: `verify-edit` can now exit 2 where it exited 0.**
|
|
13
|
+
No API changed, but a build-configuration-only edit stops being a green light, so an installed
|
|
14
|
+
hook or a pipeline that gates on the exit code will start blocking on it. A patch release must
|
|
15
|
+
never be able to turn a green pipeline red.
|
|
16
|
+
|
|
17
|
+
**Upgrade notes.**
|
|
18
|
+
|
|
19
|
+
- `verify-edit`: a change to `pom.xml`, `build.gradle`, `application*.yml`, `web.xml` or a
|
|
20
|
+
container descriptor now yields `unverified` (exit 2). `git commit --no-verify` and
|
|
21
|
+
`ask verify-edit --no-ci` are the escape hatches.
|
|
22
|
+
- `migrate-check`: published effort numbers **move down** — the same work, split by what it
|
|
23
|
+
actually is. Measured: openmrs-core 122.1 → 76.4 d, BroadleafCommerce 163.3 → 157.7,
|
|
24
|
+
alfresco 22.8 → 5.2. `blocking_count` and `readiness_score` are unchanged.
|
|
25
|
+
- `auth status`: new authoritative fields; `pro`, `pro_effective`, `pro_gating` and `pro_reason`
|
|
26
|
+
still ship, now derived from the authority and listed in `deprecated_fields`.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`verify-edit` sees the files that decide what the code compiles to** (C3-14, the second
|
|
31
|
+
reconciliation gate). Editing a `pom.xml` produced `changed_files: []`, `verdict: pass`,
|
|
32
|
+
`exit_code: 0` — and `blast_radius: {"confidence": "high", "note": "no changed files"}`.
|
|
33
|
+
git reported the file; the gate did not look at it, then said everything was fine about a
|
|
34
|
+
change that moves every source file in the repository at once.
|
|
35
|
+
- New blocking axis **`build_config_changed`**, and a new `changed_build_files` list beside
|
|
36
|
+
`changed_files`. It names what moved: `effective_compiler_release` (from → to) and
|
|
37
|
+
`dependency_delta` per coordinate.
|
|
38
|
+
- **The verdict is `unverified`, not `break`.** A build edit is neither a proven regression
|
|
39
|
+
nor computed-clean, which is exactly what `changed: null` has always meant here — so it
|
|
40
|
+
flows through the existing "could not compute" path and exits 2. No special case.
|
|
41
|
+
- Admission is by file name and deliberately narrow — `pom.xml`, `build.gradle`,
|
|
42
|
+
`settings.gradle`, `gradle.properties`, `application*.yml|properties`, `bootstrap*`,
|
|
43
|
+
`web.xml`, `weblogic*.xml`, `jboss-*.xml`, `*-context.xml`, `persistence.xml`, `beans.xml`.
|
|
44
|
+
`messages.properties` and `logback.xml` are out: every admitted file degrades a verdict, and
|
|
45
|
+
a loose rule turns the gate into noise people learn to ignore.
|
|
46
|
+
- `blast_radius` stops reporting `confidence: "high"` over a change whose reach it has not
|
|
47
|
+
measured.
|
|
48
|
+
- **Upgrade note (behaviour change):** a build-configuration-only edit now exits **2**
|
|
49
|
+
(unverified) where it exited 0 (pass). An installed pre-commit/pre-push hook will block on
|
|
50
|
+
such an edit; `git commit --no-verify` remains the escape hatch, and `--no-ci` still reports
|
|
51
|
+
without gating.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- **The migration estimate reads the report's own `auto_fix_available`** (C1-11, the first of
|
|
56
|
+
the five reconciliation gates from field evaluation #4). `migrate-check` shipped an
|
|
57
|
+
OpenRewrite recipe for most findings and then billed every one of them at the hand-edit rate.
|
|
58
|
+
Reproduced on the battery before fixing: **openmrs-core carries a recipe for 296 of 310
|
|
59
|
+
findings and costed all 310 as hand work**, for a headline of 122.1 person-days.
|
|
60
|
+
- `effort_breakdown` splits the same total into **`manual_days`** + **`mechanical_days`** +
|
|
61
|
+
`hibernate_rewrite_effort_days`. The partition is **by file**, because the severity weights
|
|
62
|
+
are per file: a file is mechanical only when *every* finding in it has a recipe, and one
|
|
63
|
+
unautomatable finding costs the whole file manually — a human is in that file either way.
|
|
64
|
+
- **The review factor is published, not buried.** `mechanical_review_factor` (0.2) carries
|
|
65
|
+
`mechanical_review_factor_basis: "assumption, not a measurement"`, so a team can substitute
|
|
66
|
+
their own number instead of inheriting ours silently. `recipes[]` lists what would run and
|
|
67
|
+
`mechanical_basis` states both denominators.
|
|
68
|
+
- **`validation_days` is `null` with a note, never `0`** — the re-test scope is a different
|
|
69
|
+
measurement (`migrate-check --blast-radius`), and `0` would read as "no re-testing needed"
|
|
70
|
+
(invariant I-3).
|
|
71
|
+
- **Published numbers move.** Measured: openmrs-core 122.1 → **76.4 d**, BroadleafCommerce
|
|
72
|
+
163.3 → **157.7 d**, alfresco 22.8 → **5.2 d**. `blocking_count` and `readiness_score` are
|
|
73
|
+
unchanged in all three — the split changes what the work is called, never what blocks.
|
|
74
|
+
|
|
75
|
+
- **`ask auth status` answers one question instead of five** (P-2). It used to publish
|
|
76
|
+
`{"status": "unauthenticated", "pro": true, "pro_reason": "early-adoption unlock"}` — every
|
|
77
|
+
field true on its own axis, and together unreadable: a buyer could not tell what they have,
|
|
78
|
+
why they have it, or what changes when it ends.
|
|
79
|
+
- One authority, `license.entitlement()`, now answers it: **`entitlement`** (`pro`/`free` —
|
|
80
|
+
what runs today), **`source`** (why: `license_key`, `early_adoption_unlock`, `free_tier`),
|
|
81
|
+
**`authenticated`** (whether a credential exists here — a *separate* fact, since today one
|
|
82
|
+
can be entitled without one), **`paywall_active`**, **`when_it_changes`** (what you lose
|
|
83
|
+
when that source stops applying) and **`free_repo_java_file_limit`** (the number the gate
|
|
84
|
+
actually compares against).
|
|
85
|
+
- `when_it_changes` states the gate in the terms the gate uses and **never quotes a price** —
|
|
86
|
+
pricing lives in `docs/PRODUCT_TIERS.md`, and a number printed here would be stale the day
|
|
87
|
+
it moves. Asserted.
|
|
88
|
+
- **`is_pro` is derived from the same authority.** It is what every gate reads
|
|
89
|
+
(`can_use`, `require_feature`), and it used to be computed beside the status block rather
|
|
90
|
+
than from it — which is how a status page came to contradict the commands next to it.
|
|
91
|
+
- `pro`, `pro_effective`, `pro_gating` and `pro_reason` are still emitted for existing
|
|
92
|
+
consumers, are derived from the authority (so they cannot drift), and are listed in the new
|
|
93
|
+
`deprecated_fields`. They are removed in a future major.
|
|
94
|
+
- `plan_status` no longer reports `unknown` for a licence file with no explicit status while
|
|
95
|
+
the authority reads that same file as active.
|
|
96
|
+
|
|
97
|
+
## [3.3.0] — 2026-07-29
|
|
98
|
+
|
|
99
|
+
**A default you must remember to disable is the wrong default for someone else's code.**
|
|
100
|
+
Telemetry moves from opt-out to opt-in. A minor and not a patch: no API changed, but a default
|
|
101
|
+
about data leaving the machine did, and that is a change a user must be told about rather than
|
|
102
|
+
discover.
|
|
103
|
+
|
|
104
|
+
**Upgrade note.** If you ever ran `ask telemetry enable` or `ask telemetry disable`, nothing
|
|
105
|
+
changes for you — your choice is on record and is honoured. If you never chose, telemetry is
|
|
106
|
+
now off and `ask telemetry enable` is how you turn it on.
|
|
107
|
+
|
|
5
108
|
### Changed
|
|
6
109
|
|
|
7
110
|
- **Telemetry is opt-in** (P-1). It was opt-out; with no explicit choice on record, nothing is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.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
|
|
@@ -283,10 +283,13 @@ Honest limits worth knowing before you rely on it:
|
|
|
283
283
|
> full Pro entitlements — no size gate, no key. The tiers below describe the model the
|
|
284
284
|
> paywall will return to later.
|
|
285
285
|
>
|
|
286
|
-
> **What that means concretely.** `ask auth status`
|
|
287
|
-
>
|
|
288
|
-
>
|
|
289
|
-
>
|
|
286
|
+
> **What that means concretely.** Ask the product: `ask auth status` answers in one block —
|
|
287
|
+
> `entitlement` (what runs today), `source` (*why* — a licence, the unlock, or the free tier),
|
|
288
|
+
> `authenticated` (whether a credential exists, which is a separate fact) and
|
|
289
|
+
> `when_it_changes` (what you lose when that source stops applying). A fresh install reads
|
|
290
|
+
> `entitlement: pro`, `source: early_adoption_unlock`, `authenticated: false` — unauthenticated
|
|
291
|
+
> *and* entitled, stated as two facts instead of one contradiction. When the unlock ends,
|
|
292
|
+
> gating returns **by repo size and automation, never by command**: `posture`, `endpoints`,
|
|
290
293
|
> `spring-audit` and `migrate-check` stay in the base tier at full output. Nothing you can run
|
|
291
294
|
> today becomes a paid-only command tomorrow.
|
|
292
295
|
|
|
@@ -245,10 +245,13 @@ Honest limits worth knowing before you rely on it:
|
|
|
245
245
|
> full Pro entitlements — no size gate, no key. The tiers below describe the model the
|
|
246
246
|
> paywall will return to later.
|
|
247
247
|
>
|
|
248
|
-
> **What that means concretely.** `ask auth status`
|
|
249
|
-
>
|
|
250
|
-
>
|
|
251
|
-
>
|
|
248
|
+
> **What that means concretely.** Ask the product: `ask auth status` answers in one block —
|
|
249
|
+
> `entitlement` (what runs today), `source` (*why* — a licence, the unlock, or the free tier),
|
|
250
|
+
> `authenticated` (whether a credential exists, which is a separate fact) and
|
|
251
|
+
> `when_it_changes` (what you lose when that source stops applying). A fresh install reads
|
|
252
|
+
> `entitlement: pro`, `source: early_adoption_unlock`, `authenticated: false` — unauthenticated
|
|
253
|
+
> *and* entitled, stated as two facts instead of one contradiction. When the unlock ends,
|
|
254
|
+
> gating returns **by repo size and automation, never by command**: `posture`, `endpoints`,
|
|
252
255
|
> `spring-audit` and `migrate-check` stay in the base tier at full output. Nothing you can run
|
|
253
256
|
> today becomes a paid-only command tomorrow.
|
|
254
257
|
|
|
@@ -6106,9 +6106,14 @@ def verify_edit_cmd(
|
|
|
6106
6106
|
|
|
6107
6107
|
Deterministic, in-loop verdict over the diff of the working tree against HEAD:
|
|
6108
6108
|
public contract, @Transactional proxy boundary, security surface, bean wiring,
|
|
6109
|
-
custom domain rules, and blast radius. Built for the
|
|
6110
|
-
git hooks. Only regressions the edit INTRODUCED (vs
|
|
6111
|
-
debt never fails an unrelated commit.
|
|
6109
|
+
custom domain rules, build/configuration changes, and blast radius. Built for the
|
|
6110
|
+
agent write-loop (MCP) and git hooks. Only regressions the edit INTRODUCED (vs
|
|
6111
|
+
HEAD) block — pre-existing debt never fails an unrelated commit.
|
|
6112
|
+
|
|
6113
|
+
Build and configuration files (pom.xml, build.gradle, application*.yml, web.xml,
|
|
6114
|
+
container descriptors, Spring XML) are reported under `changed_build_files` and
|
|
6115
|
+
make the verdict UNVERIFIED, never `pass`: their effect is not decidable from a
|
|
6116
|
+
source diff, and a compiler-release bump moves every file in the repo at once.
|
|
6112
6117
|
|
|
6113
6118
|
Custom rules: drop a `.ask/verify-rules.json` at the repo root to enforce your own
|
|
6114
6119
|
invariants (e.g. "no @RestController injects a *DaoJpa", "every /payroll endpoint
|
|
@@ -8105,47 +8110,44 @@ def auth_status_cmd() -> None:
|
|
|
8105
8110
|
"""Show current authentication and plan status."""
|
|
8106
8111
|
import json as _json
|
|
8107
8112
|
try:
|
|
8108
|
-
from sourcecode.license import
|
|
8109
|
-
|
|
8110
|
-
_license_data as _ld,
|
|
8111
|
-
is_pro as _ip,
|
|
8112
|
-
)
|
|
8113
|
+
from sourcecode.license import _license_data as _ld, entitlement as _entitlement
|
|
8114
|
+
_ent = _entitlement()
|
|
8113
8115
|
except Exception:
|
|
8114
8116
|
_ld = None
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
# While the early-adoption unlock is on, every Pro command runs. Reporting
|
|
8119
|
-
# `pro: false` here contradicted the --help header and the commands' own
|
|
8120
|
-
# behaviour: the status has to describe the gating that is actually in force,
|
|
8121
|
-
# not the credential that happens to be absent.
|
|
8122
|
-
_gating = {
|
|
8123
|
-
"pro_gating": "disabled" if _unlock else "enabled",
|
|
8124
|
-
"pro_effective": bool(_ip or _unlock),
|
|
8125
|
-
}
|
|
8126
|
-
if _unlock:
|
|
8127
|
-
_gating["pro_reason"] = "early-adoption unlock: Pro commands run without a license"
|
|
8128
|
-
|
|
8129
|
-
if not _ld:
|
|
8130
|
-
out: dict = {
|
|
8131
|
-
"status": "unauthenticated",
|
|
8132
|
-
"pro": bool(_unlock),
|
|
8133
|
-
**_gating,
|
|
8117
|
+
_ent = {
|
|
8118
|
+
"entitlement": "free", "source": "free_tier", "authenticated": False,
|
|
8119
|
+
"paywall_active": True, "when_it_changes": "", "free_repo_java_file_limit": None,
|
|
8134
8120
|
}
|
|
8135
|
-
sys.stdout.write(_json.dumps(out, ensure_ascii=False) + "\n")
|
|
8136
|
-
sys.stdout.flush()
|
|
8137
|
-
return
|
|
8138
8121
|
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
"pro":
|
|
8146
|
-
"
|
|
8147
|
-
|
|
8122
|
+
# P-2: one authority (`license.entitlement`) answers "what runs here, and why".
|
|
8123
|
+
# The legacy keys below are *derived* from it rather than computed a second
|
|
8124
|
+
# time — that is what stops `{"status": "unauthenticated", "pro": true}` from
|
|
8125
|
+
# reappearing. They are deprecated and will be removed in a future major.
|
|
8126
|
+
_pro = _ent["entitlement"] == "pro"
|
|
8127
|
+
_legacy = {
|
|
8128
|
+
"pro": _pro,
|
|
8129
|
+
"pro_effective": _pro,
|
|
8130
|
+
"pro_gating": "enabled" if _ent["paywall_active"] else "disabled",
|
|
8131
|
+
}
|
|
8132
|
+
if _ent["source"] == "early_adoption_unlock":
|
|
8133
|
+
_legacy["pro_reason"] = "early-adoption unlock: Pro commands run without a license"
|
|
8134
|
+
|
|
8135
|
+
out: dict = {
|
|
8136
|
+
"status": "authenticated" if _ent["authenticated"] else "unauthenticated",
|
|
8137
|
+
**_ent,
|
|
8138
|
+
**_legacy,
|
|
8139
|
+
"deprecated_fields": ["pro", "pro_effective", "pro_gating", "pro_reason"],
|
|
8148
8140
|
}
|
|
8141
|
+
if _ld:
|
|
8142
|
+
out.update({
|
|
8143
|
+
"auth_method": _ld.get("auth_method", "license_key"),
|
|
8144
|
+
"email": _ld.get("email", ""),
|
|
8145
|
+
"plan": _ld.get("plan", "unknown"),
|
|
8146
|
+
# The authority reads a missing status as active, so reporting
|
|
8147
|
+
# "unknown" here would contradict the entitlement in the same object.
|
|
8148
|
+
"plan_status": _ld.get("status", "active"),
|
|
8149
|
+
"validated_at": _ld.get("validated_at") or _ld.get("activated_at") or "",
|
|
8150
|
+
})
|
|
8149
8151
|
sys.stdout.write(_json.dumps(out, indent=2, ensure_ascii=False) + "\n")
|
|
8150
8152
|
sys.stdout.flush()
|
|
8151
8153
|
|
|
@@ -376,16 +376,78 @@ def _maybe_revalidate() -> None:
|
|
|
376
376
|
pass
|
|
377
377
|
|
|
378
378
|
|
|
379
|
-
def
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
def entitlement() -> dict:
|
|
380
|
+
"""The one answer to "what runs here today, and why" (P-2).
|
|
381
|
+
|
|
382
|
+
`auth status` used to publish four fields about this — `status`, `pro`,
|
|
383
|
+
`pro_gating`, `pro_effective`, plus a `pro_reason` sentence — and they read as
|
|
384
|
+
a contradiction: `{"status": "unauthenticated", "pro": true}`. Each field was
|
|
385
|
+
true on its own axis; together they told a buyer nothing about what they have,
|
|
386
|
+
why they have it, or what changes when it ends. That is the ADR-0008 pattern
|
|
387
|
+
exactly, on the commercial surface instead of an analysis one.
|
|
388
|
+
|
|
389
|
+
Every published entitlement field is derived from this dict and nowhere else.
|
|
390
|
+
|
|
391
|
+
Keys:
|
|
392
|
+
entitlement "pro" | "free" — what actually runs right now
|
|
393
|
+
source why: "license_key" | "early_adoption_unlock" | "free_tier"
|
|
394
|
+
authenticated whether a credential is on this machine (an independent
|
|
395
|
+
fact: today one can be entitled to Pro without one)
|
|
396
|
+
paywall_active whether the paywall is enforcing anything at all
|
|
397
|
+
when_it_changes what a user loses when `source` stops applying, in the
|
|
398
|
+
terms the gate actually uses — never a price
|
|
399
|
+
free_repo_java_file_limit
|
|
400
|
+
the number the size gate compares against
|
|
401
|
+
"""
|
|
402
|
+
authenticated = _license_data is not None
|
|
403
|
+
licensed = bool(
|
|
383
404
|
_license_data is not None
|
|
384
405
|
and _license_data.get("plan") == "pro"
|
|
385
406
|
and _license_data.get("status", "active") != "inactive"
|
|
386
407
|
)
|
|
387
|
-
if
|
|
388
|
-
|
|
408
|
+
if licensed:
|
|
409
|
+
source = "license_key"
|
|
410
|
+
elif _PRO_UNLOCK_ALL:
|
|
411
|
+
source = "early_adoption_unlock"
|
|
412
|
+
else:
|
|
413
|
+
source = "free_tier"
|
|
414
|
+
|
|
415
|
+
if source == "license_key":
|
|
416
|
+
when = "Your licence entitles you to Pro; nothing changes while it stays active."
|
|
417
|
+
elif source == "early_adoption_unlock":
|
|
418
|
+
when = (
|
|
419
|
+
"Pro runs without a licence during early adoption. When that ends, every command "
|
|
420
|
+
f"still runs at full output on repositories up to {_FREE_REPO_JAVA_FILE_LIMIT} Java "
|
|
421
|
+
"source files; above that, the heavy-analysis commands ask for Pro. Capability is "
|
|
422
|
+
"never what is gated — size, automation and team are."
|
|
423
|
+
)
|
|
424
|
+
else:
|
|
425
|
+
when = (
|
|
426
|
+
f"Every command runs at full output on repositories up to {_FREE_REPO_JAVA_FILE_LIMIT} "
|
|
427
|
+
"Java source files. Above that, the heavy-analysis commands require Pro."
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
"entitlement": "pro" if (licensed or _PRO_UNLOCK_ALL) else "free",
|
|
432
|
+
"source": source,
|
|
433
|
+
"authenticated": authenticated,
|
|
434
|
+
"paywall_active": not _PRO_UNLOCK_ALL,
|
|
435
|
+
"when_it_changes": when,
|
|
436
|
+
"free_repo_java_file_limit": _FREE_REPO_JAVA_FILE_LIMIT,
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _init() -> None:
|
|
441
|
+
"""Load the credential and derive the entitlement from the one authority.
|
|
442
|
+
|
|
443
|
+
`is_pro` is what every gate reads (`can_use`, `require_feature`), and it is
|
|
444
|
+
computed here from `entitlement()` rather than beside it — a second copy of
|
|
445
|
+
this rule is how `auth status` came to publish a state the commands
|
|
446
|
+
contradicted (P-2).
|
|
447
|
+
"""
|
|
448
|
+
global _license_data, is_pro
|
|
449
|
+
_license_data = _load_license_file()
|
|
450
|
+
is_pro = entitlement()["entitlement"] == "pro"
|
|
389
451
|
|
|
390
452
|
|
|
391
453
|
_init()
|
|
@@ -765,6 +765,15 @@ _BOOT3_MIGRATION_TARGETS: frozenset[str] = frozenset(
|
|
|
765
765
|
# reported as a separate hygiene metric, excluded from JDK-modernization scoring.
|
|
766
766
|
_BEST_PRACTICE_TARGETS: frozenset[str] = frozenset({"java_8_best_practice"})
|
|
767
767
|
|
|
768
|
+
#: What a file costs when every finding in it has an OpenRewrite recipe, as a
|
|
769
|
+
#: fraction of the hand-edit rate (C1-11). The recipe does not remove the work —
|
|
770
|
+
#: it turns editing into running one recipe and reviewing the diff.
|
|
771
|
+
#:
|
|
772
|
+
#: This is an **assumption, not a measurement**, and it is published as a field
|
|
773
|
+
#: (`effort_breakdown.mechanical_review_factor`) with that label so a reader can
|
|
774
|
+
#: substitute their own team's number instead of inheriting ours silently.
|
|
775
|
+
_MECHANICAL_REVIEW_FACTOR: float = 0.2
|
|
776
|
+
|
|
768
777
|
# BUG #3: the migration dimensions that feed the readiness_score aggregate, in
|
|
769
778
|
# order. jdk_modernization is deliberately NOT here — it is orthogonal upkeep debt
|
|
770
779
|
# reported on its own axis, never folded into the migration headline.
|
|
@@ -1625,12 +1634,45 @@ class MigrationReport:
|
|
|
1625
1634
|
|
|
1626
1635
|
# BUG #5: effort over MAIN findings only — N/A axes (Hibernate-6 phantom,
|
|
1627
1636
|
# test fixtures) no longer pad the estimate.
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1637
|
+
#
|
|
1638
|
+
# C1-11 (field eval #4): the estimate used to bill every file at the
|
|
1639
|
+
# hand-edit rate while the report itself shipped an OpenRewrite recipe for
|
|
1640
|
+
# most of the findings in it — on openmrs, 296 of 310 findings carry
|
|
1641
|
+
# `auto_fix_available: true` and all 310 were costed as hand work. A number
|
|
1642
|
+
# that contradicts a field in the same payload is the ADR-0008 defect, so
|
|
1643
|
+
# the estimate now reads that field.
|
|
1644
|
+
#
|
|
1645
|
+
# The unit is the file, because the weights below are per file: a file is
|
|
1646
|
+
# mechanical only when EVERY main finding in it carries a recipe. One
|
|
1647
|
+
# unautomatable finding puts a human in the file, and the rest of that
|
|
1648
|
+
# file's work comes with them.
|
|
1649
|
+
_findings_by_file: "dict[str, list[MigrationFinding]]" = {}
|
|
1650
|
+
for f in main_findings:
|
|
1651
|
+
_findings_by_file.setdefault(f.source_file, []).append(f)
|
|
1652
|
+
_mechanical_files = {
|
|
1653
|
+
path for path, items in _findings_by_file.items()
|
|
1654
|
+
if items and all(bool(i.openrewrite_recipe) for i in items)
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
def _weighted(files: "set[str]") -> float:
|
|
1658
|
+
return (
|
|
1659
|
+
len(critical_files & files) * 0.5
|
|
1660
|
+
+ len(high_files & files) * 0.25
|
|
1661
|
+
+ len(medium_files & files) * 0.1
|
|
1662
|
+
+ len(low_files & files) * 0.05
|
|
1663
|
+
)
|
|
1664
|
+
|
|
1665
|
+
_all_effort_files = critical_files | high_files | medium_files | low_files
|
|
1666
|
+
_manual_effort = _weighted(_all_effort_files - _mechanical_files)
|
|
1667
|
+
# A recipe does not remove the work, it changes what the work is: run the
|
|
1668
|
+
# recipe once, then review the diff. Reviewing is charged at a fraction of
|
|
1669
|
+
# editing. The fraction is an assumption, not a measurement, so it is
|
|
1670
|
+
# published as a field rather than buried in this expression.
|
|
1671
|
+
_mechanical_raw = _weighted(_all_effort_files & _mechanical_files)
|
|
1672
|
+
_mechanical_effort = _mechanical_raw * _MECHANICAL_REVIEW_FACTOR
|
|
1673
|
+
_file_effort = _manual_effort + _mechanical_effort
|
|
1674
|
+
_auto_fixable = sum(1 for f in main_findings if f.openrewrite_recipe)
|
|
1675
|
+
_recipes = sorted({f.openrewrite_recipe for f in main_findings if f.openrewrite_recipe})
|
|
1634
1676
|
# BUG #1 (v1.70.0): when the Hibernate 5→6 axis APPLIES, fold its measured
|
|
1635
1677
|
# rewrite effort (risk_matrix → total_effort_range_days) into the headline
|
|
1636
1678
|
# estimate. Previously a Hibernate-5 project whose ${hibernateVersion} was
|
|
@@ -1646,6 +1688,33 @@ class MigrationReport:
|
|
|
1646
1688
|
self.estimated_effort_days = round(_file_effort + _hib_effort, 1)
|
|
1647
1689
|
self.effort_breakdown = {
|
|
1648
1690
|
"findings_effort_days": round(_file_effort, 1),
|
|
1691
|
+
# C1-11: the same total, split by what the work actually is.
|
|
1692
|
+
"mechanical_days": round(_mechanical_effort, 1),
|
|
1693
|
+
"manual_days": round(_manual_effort, 1),
|
|
1694
|
+
# Not estimated rather than estimated at zero: the re-test scope is a
|
|
1695
|
+
# different measurement (which endpoints the changed files reach), and
|
|
1696
|
+
# `migrate-check --blast-radius` is what produces it. I-3: a fact that
|
|
1697
|
+
# cannot be decided says so.
|
|
1698
|
+
"validation_days": None,
|
|
1699
|
+
"validation_days_note": (
|
|
1700
|
+
"Not estimated here — the re-test scope is the endpoints the changed "
|
|
1701
|
+
"files reach. Run `migrate-check --blast-radius` for that ordering."
|
|
1702
|
+
),
|
|
1703
|
+
"mechanical_basis": (
|
|
1704
|
+
f"{_auto_fixable} of {len(main_findings)} findings carry an OpenRewrite "
|
|
1705
|
+
f"recipe; {len(_mechanical_files & _all_effort_files)} of "
|
|
1706
|
+
f"{len(_all_effort_files)} affected files have a recipe for *every* "
|
|
1707
|
+
f"finding in them and are costed at {_MECHANICAL_REVIEW_FACTOR:g}× the "
|
|
1708
|
+
"hand-edit rate (run the recipe, review the diff). A file with one "
|
|
1709
|
+
"unautomatable finding is costed manually in full — a human is in that "
|
|
1710
|
+
"file either way."
|
|
1711
|
+
),
|
|
1712
|
+
"mechanical_review_factor": _MECHANICAL_REVIEW_FACTOR,
|
|
1713
|
+
"mechanical_review_factor_basis": "assumption, not a measurement",
|
|
1714
|
+
"auto_fixable_findings": _auto_fixable,
|
|
1715
|
+
"auto_fixable_files": len(_mechanical_files & _all_effort_files),
|
|
1716
|
+
"affected_files": len(_all_effort_files),
|
|
1717
|
+
"recipes": _recipes,
|
|
1649
1718
|
"hibernate_rewrite_effort_days": round(_hib_effort, 1),
|
|
1650
1719
|
"hibernate_rewrite_range": (
|
|
1651
1720
|
hib.total_effort_range_days if (_hibernate_applies and hib is not None) else None
|
|
@@ -1809,7 +1878,20 @@ class MigrationReport:
|
|
|
1809
1878
|
f"(critical: {main_crit}, high: {main_high})"
|
|
1810
1879
|
+ (f" [+{nb} in test/generated, non-blocking]" if nb else ""),
|
|
1811
1880
|
f"Affected files: {self.summary.get('affected_files', 0)}",
|
|
1812
|
-
f"Estimated effort: {self.estimated_effort_days}d"
|
|
1881
|
+
f"Estimated effort: {self.estimated_effort_days}d"
|
|
1882
|
+
+ (
|
|
1883
|
+
f" ({self.effort_breakdown.get('manual_days', 0)}d manual + "
|
|
1884
|
+
f"{self.effort_breakdown.get('mechanical_days', 0)}d mechanical"
|
|
1885
|
+
+ (f" + {self.effort_breakdown.get('hibernate_rewrite_effort_days')}d hibernate"
|
|
1886
|
+
if self.effort_breakdown.get("hibernate_rewrite_effort_days") else "")
|
|
1887
|
+
+ "; validation not estimated)"
|
|
1888
|
+
if self.effort_breakdown else ""
|
|
1889
|
+
),
|
|
1890
|
+
(
|
|
1891
|
+
f" {self.effort_breakdown.get('auto_fixable_findings', 0)} findings carry an "
|
|
1892
|
+
f"OpenRewrite recipe — see effort_breakdown.recipes"
|
|
1893
|
+
if self.effort_breakdown.get("auto_fixable_findings") else ""
|
|
1894
|
+
),
|
|
1813
1895
|
"",
|
|
1814
1896
|
]
|
|
1815
1897
|
|