sourcecode 3.1.0__tar.gz → 3.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. {sourcecode-3.1.0 → sourcecode-3.1.1}/CHANGELOG.md +39 -0
  2. {sourcecode-3.1.0 → sourcecode-3.1.1}/PKG-INFO +3 -3
  3. {sourcecode-3.1.0 → sourcecode-3.1.1}/README.md +2 -2
  4. {sourcecode-3.1.0 → sourcecode-3.1.1}/pyproject.toml +1 -1
  5. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/cli.py +74 -0
  7. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/migrate_check.py +22 -1
  8. sourcecode-3.1.1/src/sourcecode/migration_blast.py +232 -0
  9. {sourcecode-3.1.0 → sourcecode-3.1.1}/.github/workflows/build-windows.yml +0 -0
  10. {sourcecode-3.1.0 → sourcecode-3.1.1}/.gitignore +0 -0
  11. {sourcecode-3.1.0 → sourcecode-3.1.1}/.ruff.toml +0 -0
  12. {sourcecode-3.1.0 → sourcecode-3.1.1}/CONTRIBUTING.md +0 -0
  13. {sourcecode-3.1.0 → sourcecode-3.1.1}/LICENSE +0 -0
  14. {sourcecode-3.1.0 → sourcecode-3.1.1}/SECURITY.md +0 -0
  15. {sourcecode-3.1.0 → sourcecode-3.1.1}/raw +0 -0
  16. {sourcecode-3.1.0 → sourcecode-3.1.1}/scripts/compare_integration_engines.py +0 -0
  17. {sourcecode-3.1.0 → sourcecode-3.1.1}/scripts/customer_smoke_test.sh +0 -0
  18. {sourcecode-3.1.0 → sourcecode-3.1.1}/scripts/generate_jdk_exports.py +0 -0
  19. {sourcecode-3.1.0 → sourcecode-3.1.1}/scripts/perf_harness.py +0 -0
  20. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/adaptive_scanner.py +0 -0
  21. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/archetype.py +0 -0
  22. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/architectural_baseline.py +0 -0
  23. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/architectural_delta.py +0 -0
  24. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/architecture_analyzer.py +0 -0
  25. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/architecture_summary.py +0 -0
  26. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/ast_extractor.py +0 -0
  27. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/cache.py +0 -0
  28. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/call_surface.py +0 -0
  29. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/caller_metrics.py +0 -0
  30. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/canonical_ir.py +0 -0
  31. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/chain_rules.py +0 -0
  32. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/change_plan.py +0 -0
  33. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/cir_graphs.py +0 -0
  34. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/classifier.py +0 -0
  35. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/code_notes_analyzer.py +0 -0
  36. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/compare.py +0 -0
  37. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/confidence_analyzer.py +0 -0
  38. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/constraint_diff.py +0 -0
  39. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/context_cache.py +0 -0
  40. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/context_graph.py +0 -0
  41. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/context_scorer.py +0 -0
  42. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/context_summarizer.py +0 -0
  43. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/contract_diff.py +0 -0
  44. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/contract_model.py +0 -0
  45. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/contract_pipeline.py +0 -0
  46. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/coverage_parser.py +0 -0
  47. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/dependency_analyzer.py +0 -0
  48. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/deployment_prefix.py +0 -0
  49. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/__init__.py +0 -0
  50. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/base.py +0 -0
  51. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
  52. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/dart.py +0 -0
  53. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/dotnet.py +0 -0
  54. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/elixir.py +0 -0
  55. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/go.py +0 -0
  56. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/heuristic.py +0 -0
  57. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/hybrid.py +0 -0
  58. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/java.py +0 -0
  59. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
  60. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/nodejs.py +0 -0
  61. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/parsers.py +0 -0
  62. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/php.py +0 -0
  63. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/project.py +0 -0
  64. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/python.py +0 -0
  65. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/ruby.py +0 -0
  66. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/rust.py +0 -0
  67. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/systems.py +0 -0
  68. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/terraform.py +0 -0
  69. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/detectors/tooling.py +0 -0
  70. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/doc_analyzer.py +0 -0
  71. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
  72. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/endpoint_literals.py +0 -0
  73. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/endpoint_metrics.py +0 -0
  74. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/entrypoint_classifier.py +0 -0
  75. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/env_analyzer.py +0 -0
  76. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/envelope.py +0 -0
  77. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/error_schema.py +0 -0
  78. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/evidence_provider.py +0 -0
  79. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/explain.py +0 -0
  80. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/file_chunker.py +0 -0
  81. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/file_classifier.py +0 -0
  82. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/filter_surface.py +0 -0
  83. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/format_contract.py +0 -0
  84. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/fqn_utils.py +0 -0
  85. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/git_analyzer.py +0 -0
  86. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/graph_analyzer.py +0 -0
  87. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/graph_evidence.py +0 -0
  88. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/hibernate_strat.py +0 -0
  89. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/jdk_exports.py +0 -0
  90. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/license.py +0 -0
  91. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/__init__.py +0 -0
  92. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  93. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  94. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  95. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  96. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  97. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/orchestrator.py +0 -0
  98. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/registry.py +0 -0
  99. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/runner.py +0 -0
  100. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp/server.py +0 -0
  101. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/mcp_nudge.py +0 -0
  102. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/metrics_analyzer.py +0 -0
  103. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/openapi_surface.py +0 -0
  104. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/output_budget.py +0 -0
  105. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/parse_cache.py +0 -0
  106. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/path_filters.py +0 -0
  107. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/perf.py +0 -0
  108. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/posture.py +0 -0
  109. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/pr_comment_renderer.py +0 -0
  110. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/pr_impact.py +0 -0
  111. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/prepare_context.py +0 -0
  112. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/progress.py +0 -0
  113. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/ranking_engine.py +0 -0
  114. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/readiness_timeline.py +0 -0
  115. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/reconciliation.py +0 -0
  116. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/redactor.py +0 -0
  117. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/relevance_scorer.py +0 -0
  118. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/rename_refactor.py +0 -0
  119. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/repo_classifier.py +0 -0
  120. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/repository_ir.py +0 -0
  121. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/__init__.py +0 -0
  122. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/context.py +0 -0
  123. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/errors.py +0 -0
  124. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/executor.py +0 -0
  125. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/planner.py +0 -0
  126. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/query.py +0 -0
  127. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/request.py +0 -0
  128. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/resolution.py +0 -0
  129. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/result.py +0 -0
  130. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/retriever.py +0 -0
  131. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/runtime.py +0 -0
  132. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps.py +0 -0
  133. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  134. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
  135. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
  136. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
  137. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
  138. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  139. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/ris.py +0 -0
  140. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/runtime_classifier.py +0 -0
  141. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/scanner.py +0 -0
  142. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/schema.py +0 -0
  143. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  144. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/security_config.py +0 -0
  145. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/security_posture.py +0 -0
  146. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/semantic_analyzer.py +0 -0
  147. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/semantic_impact_engine.py +0 -0
  148. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/semantic_integration_engine.py +0 -0
  149. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/semantic_services.py +0 -0
  150. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/serializer.py +0 -0
  151. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_event_topology.py +0 -0
  152. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_findings.py +0 -0
  153. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_impact.py +0 -0
  154. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_model.py +0 -0
  155. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_profiles.py +0 -0
  156. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_properties.py +0 -0
  157. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_security_audit.py +0 -0
  158. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_semantic.py +0 -0
  159. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
  160. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/summarizer.py +0 -0
  161. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/__init__.py +0 -0
  162. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/config.py +0 -0
  163. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/consent.py +0 -0
  164. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/events.py +0 -0
  165. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/filters.py +0 -0
  166. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/telemetry/transport.py +0 -0
  167. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/token_estimate.py +0 -0
  168. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/tree_utils.py +0 -0
  169. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/type_usage_surface.py +0 -0
  170. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/validation_inference.py +0 -0
  171. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/validation_surface.py +0 -0
  172. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/verify_edit.py +0 -0
  173. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/verify_repo.py +0 -0
  174. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/verify_rules.py +0 -0
  175. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/version_check.py +0 -0
  176. {sourcecode-3.1.0 → sourcecode-3.1.1}/src/sourcecode/workspace.py +0 -0
  177. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/functions/README.md +0 -0
  178. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/functions/get-license/index.ts +0 -0
  179. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  180. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/functions/telemetry/index.ts +0 -0
  181. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/sql/license_event_ordering.sql +0 -0
  182. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/sql/licensing_schema.sql +0 -0
  183. {sourcecode-3.1.0 → sourcecode-3.1.1}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - **`migrate-check --blast-radius` states the regression scope behind each blocker.** The
8
+ finding list says what must change; a migration plan is built around what has to be
9
+ re-tested when that change lands. Each affected product file now resolves to the types
10
+ it declares and runs through the same caller traversal `impact-chain` uses, so the
11
+ block reports the HTTP endpoints whose call path passes through it, ranked. Measured on
12
+ BroadleafCommerce: 62.3% of the request surface (137 of 220 endpoints) runs through a
13
+ file with a finding, and 52 of the 166 affected files carry all of it. Implemented as a
14
+ rule over the existing IR — no new detector, ~3 ms per affected file on top of an IR
15
+ build shared with `impact-chain` / `explain` through the context cache.
16
+ - Reach is regression scope, never build breakage: a `javax→jakarta` finding fails
17
+ compilation whether or not any endpoint reaches it, and the payload says so.
18
+ - Unresolved is not zero — a file whose declared types the IR never resolved is listed
19
+ in `files_unresolved` with reach *unknown*.
20
+ - A repository the IR resolves no endpoints in (alfresco's webscript framework,
21
+ openmrs-core) gets `request_surface_share: null` and a note that ranking fell back to
22
+ caller counts, never a manufactured `0.0`.
23
+ - Opt-in and additive: without the flag the report is byte-identical to before, and the
24
+ key is absent rather than `null`.
25
+
26
+ ## [3.1.1] — 2026-07-28
27
+
28
+ Documentation only; no behaviour changed.
29
+
30
+ - **`contracts.md` gains an enforcement-maturity section**: which contract kinds have been
31
+ run against real code and with what result, the one false block found and fixed (a
32
+ fully-qualified security annotation reading as no guard, 3.1.0), and what is still
33
+ unproven — the GitHub Actions workflow has never run in a hosted runner. Plus a
34
+ three-week rollout that starts advisory (`--fail-on never`), because a contract whose
35
+ first act is blocking a merge gets deleted.
36
+ - **`migrate-check.md` documents the output-size guard** added in 3.1.0: the measured
37
+ sizes that motivated it, the emitted `OUTPUT_TOO_LARGE` payload, and the four ways past
38
+ it in the order worth trying.
39
+ - Doc-accuracy tests cover the new claims: the rollout flags exist, every documented
40
+ contract kind is implemented, and the quoted guard hint is the one the code emits.
41
+
3
42
  ## [3.1.0] — 2026-07-28
4
43
 
5
44
  **Posture answers about requests, not only about beans.** Milestone M4 of the strategic
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 3.1.0
3
+ Version: 3.1.1
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -139,7 +139,7 @@ The system map: module graph, dependency views, REST surface, per-class summarie
139
139
  → [reference](docs/USER_GUIDE.md#core-commands)
140
140
 
141
141
  ### 4 · Migration & Modernization
142
- Is this codebase ready to upgrade? Per-dimension readiness (Jakarta / Spring Boot / JDK / Hibernate), located blockers, and an effort estimate.
142
+ Is this codebase ready to upgrade? Per-dimension readiness (Jakarta / Spring Boot / JDK / Hibernate), located blockers, an effort estimate, and — with `--blast-radius` — the endpoints whose call path runs through each blocker, so the re-test plan is ordered by regression scope.
143
143
  `ask migrate-check` · `ask modernize`
144
144
  → [migrate-check reference](docs/migrate-check.md) · [MODERNIZATION.md](docs/MODERNIZATION.md)
145
145
 
@@ -233,7 +233,7 @@ Matching endpoints report `policy: "custom"` and drop out of the `no_security_si
233
233
  |-----|----------------|
234
234
  | [USER_GUIDE.md](docs/USER_GUIDE.md) | Full command reference, flags, output schema, workflows |
235
235
  | [contracts.md](docs/contracts.md) | Declare invariants in `.ask/contracts.yml`, enforce them in the edit loop and in CI |
236
- | [posture.md](docs/posture.md) | What a profile set actually wires, and what could not be decided (experimental) |
236
+ | [posture.md](docs/posture.md) | What a profile set actually wires — down to which endpoints its filter chain permits — and what could not be decided (experimental) |
237
237
  | [migrate-check.md](docs/migrate-check.md) | Migration rule catalogue (MIG-001..043) + Hibernate stratification |
238
238
  | [MODERNIZATION.md](docs/MODERNIZATION.md) | The modernization product: assess → understand → plan → execute |
239
239
  | [PRODUCT_TIERS.md](docs/PRODUCT_TIERS.md) | Free vs Pro, pricing model |
@@ -101,7 +101,7 @@ The system map: module graph, dependency views, REST surface, per-class summarie
101
101
  → [reference](docs/USER_GUIDE.md#core-commands)
102
102
 
103
103
  ### 4 · Migration & Modernization
104
- Is this codebase ready to upgrade? Per-dimension readiness (Jakarta / Spring Boot / JDK / Hibernate), located blockers, and an effort estimate.
104
+ Is this codebase ready to upgrade? Per-dimension readiness (Jakarta / Spring Boot / JDK / Hibernate), located blockers, an effort estimate, and — with `--blast-radius` — the endpoints whose call path runs through each blocker, so the re-test plan is ordered by regression scope.
105
105
  `ask migrate-check` · `ask modernize`
106
106
  → [migrate-check reference](docs/migrate-check.md) · [MODERNIZATION.md](docs/MODERNIZATION.md)
107
107
 
@@ -195,7 +195,7 @@ Matching endpoints report `policy: "custom"` and drop out of the `no_security_si
195
195
  |-----|----------------|
196
196
  | [USER_GUIDE.md](docs/USER_GUIDE.md) | Full command reference, flags, output schema, workflows |
197
197
  | [contracts.md](docs/contracts.md) | Declare invariants in `.ask/contracts.yml`, enforce them in the edit loop and in CI |
198
- | [posture.md](docs/posture.md) | What a profile set actually wires, and what could not be decided (experimental) |
198
+ | [posture.md](docs/posture.md) | What a profile set actually wires — down to which endpoints its filter chain permits — and what could not be decided (experimental) |
199
199
  | [migrate-check.md](docs/migrate-check.md) | Migration rule catalogue (MIG-001..043) + Hibernate stratification |
200
200
  | [MODERNIZATION.md](docs/MODERNIZATION.md) | The modernization product: assess → understand → plan → execute |
201
201
  | [PRODUCT_TIERS.md](docs/PRODUCT_TIERS.md) | Free vs Pro, pricing model |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "3.1.0"
7
+ version = "3.1.1"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "3.1.0"
7
+ __version__ = "3.1.1"
@@ -6201,6 +6201,65 @@ def posture_cmd(
6201
6201
  # ── Spring Boot Migration Check ───────────────────────────────────────────────
6202
6202
 
6203
6203
 
6204
+ def _migration_blast_radius(
6205
+ target: Path,
6206
+ file_list: list[str],
6207
+ report: "object",
6208
+ depth: int,
6209
+ ) -> dict:
6210
+ """Resolve the regression scope behind the migration findings (--blast-radius).
6211
+
6212
+ Reuses the shared context-cache CIR — the same one `impact-chain`, `explain` and
6213
+ `cache warm` use — so a warmed repository skips the Java parse. Every failure mode
6214
+ degrades to a block that says what could not be computed; it never fabricates reach
6215
+ and never breaks the scan that already succeeded.
6216
+ """
6217
+ from sourcecode.migration_blast import compute_migration_blast, SCHEMA_VERSION
6218
+
6219
+ _depth = max(1, min(int(depth), 8))
6220
+ findings = list(getattr(report, "findings", []) or [])
6221
+
6222
+ def _degraded(reason: str) -> dict:
6223
+ return {
6224
+ "schema_version": SCHEMA_VERSION,
6225
+ "depth": _depth,
6226
+ "status": "unavailable",
6227
+ "endpoints_in_repo": 0,
6228
+ "endpoints_reached": 0,
6229
+ "request_surface_share": None,
6230
+ "files_analyzed": 0,
6231
+ "files_on_request_path": 0,
6232
+ "files_off_request_path": 0,
6233
+ "files_unresolved": [],
6234
+ "files": [],
6235
+ "files_omitted": 0,
6236
+ "notes": [reason],
6237
+ }
6238
+
6239
+ if not file_list:
6240
+ return _degraded(
6241
+ "No Java sources in this repository — there is no call graph to traverse, "
6242
+ "so no regression scope can be attributed."
6243
+ )
6244
+
6245
+ try:
6246
+ from sourcecode import context_cache as _ctxcache
6247
+ from sourcecode.context_graph import ContextGraph
6248
+ from sourcecode.spring_model import SpringSemanticModel
6249
+
6250
+ try:
6251
+ cir, _ = _ctxcache.get_or_build_cir(_resolve_repo_root(target), target, file_list)
6252
+ except Exception:
6253
+ cir = ContextGraph.build(file_list, target).cir
6254
+ model = SpringSemanticModel.build(cir)
6255
+ return compute_migration_blast(cir, model, findings, depth=_depth)
6256
+ except Exception as exc: # pragma: no cover - defensive
6257
+ return _degraded(
6258
+ f"Blast radius unavailable: {type(exc).__name__} while building the "
6259
+ "repository IR. The migration findings above are unaffected."
6260
+ )
6261
+
6262
+
6204
6263
  @app.command("migrate-check")
6205
6264
  def migrate_check_cmd(
6206
6265
  path: Path = typer.Argument(
@@ -6265,6 +6324,18 @@ def migrate_check_cmd(
6265
6324
  False, "--force",
6266
6325
  help="Emit to stdout even when the report exceeds the output-size guard.",
6267
6326
  ),
6327
+ blast_radius: bool = typer.Option(
6328
+ False, "--blast-radius",
6329
+ help=(
6330
+ "Rank affected product files by the HTTP endpoints whose call path runs "
6331
+ "through them (regression scope), using the same caller traversal as "
6332
+ "impact-chain. Builds the repository IR — slower than a plain scan."
6333
+ ),
6334
+ ),
6335
+ depth: int = typer.Option(
6336
+ 4, "--depth",
6337
+ help="Caller BFS depth for --blast-radius (1-8, default: 4).",
6338
+ ),
6268
6339
  ) -> None:
6269
6340
  """Spring Boot 2→3 migration readiness: detect javax→jakarta namespace blockers.
6270
6341
 
@@ -6366,6 +6437,9 @@ def migrate_check_cmd(
6366
6437
  if _file_limitations:
6367
6438
  report.limitations.extend(_file_limitations)
6368
6439
 
6440
+ if blast_radius:
6441
+ report.blast_radius = _migration_blast_radius(target, file_list, report, depth)
6442
+
6369
6443
  if format == "text":
6370
6444
  output = report.to_text(min_severity=min_severity)
6371
6445
  else:
@@ -1402,6 +1402,11 @@ class MigrationReport:
1402
1402
  # exposure map, call-chain detection, upgrade-vs-rewrite verdict). Attached
1403
1403
  # by run_migrate_check; None when not computed.
1404
1404
  hibernate: Optional["HibernateStratification"] = None
1405
+ # Regression scope behind the findings: the endpoints whose call path runs
1406
+ # through each affected file (migration_blast.compute_migration_blast).
1407
+ # Opt-in — attached by the CLI under --blast-radius; None when not computed,
1408
+ # and then absent from the payload so the default report stays byte-identical.
1409
+ blast_radius: Optional[dict] = None
1405
1410
 
1406
1411
  def finalize(self) -> "MigrationReport":
1407
1412
  if not self.generated_at:
@@ -1682,7 +1687,7 @@ class MigrationReport:
1682
1687
  return self
1683
1688
 
1684
1689
  def to_dict(self) -> dict:
1685
- return {
1690
+ d: dict = {
1686
1691
  "schema_version": self.schema_version,
1687
1692
  "generated_at": self.generated_at,
1688
1693
  "repo_id": self.repo_id,
@@ -1718,6 +1723,11 @@ class MigrationReport:
1718
1723
  "limitations": self.limitations,
1719
1724
  "metadata": self.metadata,
1720
1725
  }
1726
+ # Additive and opt-in: the key appears only when --blast-radius computed it,
1727
+ # so a default run emits exactly the payload it emitted before.
1728
+ if self.blast_radius is not None:
1729
+ d["blast_radius"] = self.blast_radius
1730
+ return d
1721
1731
 
1722
1732
  def to_compact_dict(self, top_n: int = _COMPACT_TOP_N) -> dict:
1723
1733
  """Bounded, decision-grade projection of `to_dict()`.
@@ -1754,6 +1764,11 @@ class MigrationReport:
1754
1764
  if isinstance(h.get(_col), list):
1755
1765
  h[_col] = _cap_collection(h[_col], top_n)
1756
1766
  compact["hibernate"] = h
1767
+ blast = full.get("blast_radius")
1768
+ if isinstance(blast, dict) and isinstance(blast.get("files"), list):
1769
+ b = dict(blast)
1770
+ b["files"] = _cap_collection(b["files"], top_n)
1771
+ compact["blast_radius"] = b
1757
1772
  return compact
1758
1773
 
1759
1774
  def to_text(self, min_severity: str = "low") -> str:
@@ -1802,6 +1817,12 @@ class MigrationReport:
1802
1817
  lines.append(self.hibernate.to_text())
1803
1818
  lines.append("")
1804
1819
 
1820
+ if self.blast_radius is not None:
1821
+ from sourcecode.migration_blast import render_blast_text
1822
+
1823
+ lines.append(render_blast_text(self.blast_radius))
1824
+ lines.append("")
1825
+
1805
1826
  if not visible:
1806
1827
  lines.append("No findings at or above selected severity.")
1807
1828
  return "\n".join(lines)
@@ -0,0 +1,232 @@
1
+ """Migration blast radius — which request surface a migration finding sits under.
2
+
3
+ `migrate-check` answers *what must change*: the files whose `javax.*` imports, XML
4
+ descriptors and build coordinates block a Spring Boot 2→3 upgrade. It does not answer
5
+ *what has to be re-tested when that change lands*, which is the question a migration
6
+ plan is actually built around.
7
+
8
+ This module answers it as a rule over the existing IR — no new detector, no second
9
+ traversal engine. Each affected file resolves to the types it declares, those types are
10
+ run through the same caller BFS `impact-chain` uses (`spring_impact._bfs_callers` +
11
+ `_collect_endpoints`), and the reachable HTTP endpoints become that file's re-test set.
12
+
13
+ Honesty contract:
14
+ * A `javax→jakarta` finding breaks compilation whether or not an endpoint reaches it.
15
+ `endpoints_reached` ranks *regression scope*, never "what fails to build".
16
+ * A file whose declared types the IR never resolved is reported in `files_unresolved`
17
+ with reach `unknown` — never folded into the zero-reach bucket.
18
+ * A repository the IR sees no endpoints in gets `request_surface_share: null` and a
19
+ note saying ranking fell back to caller counts, never a manufactured 0.0.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import TYPE_CHECKING, Any, Optional
25
+
26
+ from sourcecode.spring_impact import _bfs_callers, _collect_endpoints
27
+
28
+ if TYPE_CHECKING: # pragma: no cover - typing only
29
+ from sourcecode.canonical_ir import CanonicalRepositoryIR
30
+ from sourcecode.migrate_check import MigrationFinding
31
+ from sourcecode.spring_model import SpringSemanticModel
32
+
33
+ SCHEMA_VERSION = "1.0"
34
+
35
+ DEFAULT_DEPTH = 4
36
+ DEFAULT_TOP_N = 25
37
+ _ENDPOINTS_PER_FILE = 10
38
+
39
+ _SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
40
+ _TYPE_KINDS = ("class", "interface", "enum", "annotation")
41
+
42
+ _REACH_NOTE = (
43
+ "endpoints_reached ranks REGRESSION SCOPE — the requests whose call path runs "
44
+ "through the file — not what fails to build. A javax→jakarta finding breaks "
45
+ "compilation whether or not any endpoint reaches it."
46
+ )
47
+
48
+
49
+ def _types_by_file(cir: "CanonicalRepositoryIR") -> dict[str, list[str]]:
50
+ """Map source file → the type FQNs declared in it, from the IR's graph nodes."""
51
+ raw = getattr(cir, "_raw_ir", None) or {}
52
+ nodes = (raw.get("graph") or {}).get("nodes") or []
53
+ out: dict[str, list[str]] = {}
54
+ for node in nodes:
55
+ if not isinstance(node, dict):
56
+ continue
57
+ src = node.get("source_file") or ""
58
+ fqn = node.get("fqn") or ""
59
+ if not src or not fqn or "#" in fqn:
60
+ continue
61
+ if (node.get("symbol_kind") or node.get("type")) not in _TYPE_KINDS:
62
+ continue
63
+ out.setdefault(src, []).append(fqn)
64
+ for src in out:
65
+ out[src] = sorted(set(out[src]))
66
+ return out
67
+
68
+
69
+ def _max_severity(findings: list["MigrationFinding"]) -> str:
70
+ return min(
71
+ (f.severity for f in findings),
72
+ key=lambda s: _SEVERITY_ORDER.get(s, 3),
73
+ default="low",
74
+ )
75
+
76
+
77
+ def compute_migration_blast(
78
+ cir: "CanonicalRepositoryIR",
79
+ model: "SpringSemanticModel",
80
+ findings: list["MigrationFinding"],
81
+ *,
82
+ depth: int = DEFAULT_DEPTH,
83
+ top_n: int = DEFAULT_TOP_N,
84
+ ) -> dict:
85
+ """Resolve the request surface behind each migration finding.
86
+
87
+ Args:
88
+ cir: CanonicalRepositoryIR for the same repository that was scanned.
89
+ model: SpringSemanticModel built from that CIR (endpoint index).
90
+ findings: MigrationFinding list from `run_migrate_check`.
91
+ depth: Caller BFS depth, the same knob `impact-chain --depth` exposes.
92
+ top_n: Files retained in `files`; the rest are counted in `files_omitted`.
93
+
94
+ Returns a JSON-serializable dict. Never raises on a partial IR: anything it
95
+ could not resolve is reported as unresolved rather than dropped.
96
+ """
97
+ by_file: dict[str, list["MigrationFinding"]] = {}
98
+ non_source = 0
99
+ non_product = 0
100
+ for f in findings:
101
+ src = getattr(f, "source_file", "") or ""
102
+ if not src.endswith(".java"):
103
+ # XML descriptors and build coordinates carry no type the call graph
104
+ # can reach; they are counted, never ranked.
105
+ non_source += 1
106
+ continue
107
+ if getattr(f, "code_context", "main") != "main":
108
+ # Test and generated sources are outside the product blast radius, the
109
+ # same boundary blocking_count draws.
110
+ non_product += 1
111
+ continue
112
+ by_file.setdefault(src, []).append(f)
113
+
114
+ declared = _types_by_file(cir)
115
+ impl_graph = getattr(cir, "implementation_graph", None)
116
+ reverse_graph = getattr(cir, "reverse_graph", None) or {}
117
+ endpoints_in_repo = len(getattr(cir, "endpoints", None) or [])
118
+
119
+ rows: list[dict[str, Any]] = []
120
+ unresolved: list[str] = []
121
+ reached_ids: set[str] = set()
122
+
123
+ for src in sorted(by_file):
124
+ seeds = declared.get(src) or []
125
+ if not seeds:
126
+ unresolved.append(src)
127
+ continue
128
+ direct, indirect, truncated, *_rest = _bfs_callers(
129
+ seeds, reverse_graph, depth, impl_graph=impl_graph,
130
+ )
131
+ endpoints = _collect_endpoints(list(direct) + list(indirect), seeds, model)
132
+ for ep in endpoints:
133
+ reached_ids.add(ep.endpoint_id)
134
+ file_findings = by_file[src]
135
+ shown = sorted(
136
+ (e.to_dict() for e in endpoints),
137
+ key=lambda e: (e["path"], e["method"], e["endpoint_id"]),
138
+ )
139
+ rows.append({
140
+ "file": src,
141
+ "max_severity": _max_severity(file_findings),
142
+ "finding_count": len(file_findings),
143
+ "rule_ids": sorted({f.rule_id for f in file_findings}),
144
+ "types": seeds,
145
+ "direct_callers": len(direct),
146
+ "indirect_callers": len(indirect),
147
+ "traversal_truncated": bool(truncated),
148
+ "endpoints_reached": len(endpoints),
149
+ "endpoints": shown[:_ENDPOINTS_PER_FILE],
150
+ "endpoints_omitted": max(0, len(shown) - _ENDPOINTS_PER_FILE),
151
+ })
152
+
153
+ rows.sort(key=lambda r: (
154
+ -r["endpoints_reached"],
155
+ -(r["direct_callers"] + r["indirect_callers"]),
156
+ _SEVERITY_ORDER.get(r["max_severity"], 3),
157
+ r["file"],
158
+ ))
159
+
160
+ on_path = sum(1 for r in rows if r["endpoints_reached"] > 0)
161
+ notes: list[str] = [_REACH_NOTE]
162
+ if endpoints_in_repo:
163
+ share = round(len(reached_ids) / endpoints_in_repo, 4)
164
+ else:
165
+ share = None
166
+ notes.append(
167
+ "The IR resolved no HTTP endpoints in this repository, so no request "
168
+ "surface can be attributed — files are ranked by caller count alone."
169
+ )
170
+ if unresolved:
171
+ notes.append(
172
+ f"{len(unresolved)} file(s) carry a finding but declare no type the IR "
173
+ "resolved (parse gap or non-type source); their reach is unknown, not zero."
174
+ )
175
+ if any(r["traversal_truncated"] for r in rows):
176
+ notes.append(
177
+ "At least one traversal hit the hub-class guard and was capped — those "
178
+ "files' reach is a lower bound."
179
+ )
180
+
181
+ return {
182
+ "schema_version": SCHEMA_VERSION,
183
+ "depth": depth,
184
+ "endpoints_in_repo": endpoints_in_repo,
185
+ "endpoints_reached": len(reached_ids),
186
+ "request_surface_share": share,
187
+ "files_analyzed": len(rows),
188
+ "files_on_request_path": on_path,
189
+ "files_off_request_path": len(rows) - on_path,
190
+ "files_unresolved": sorted(unresolved),
191
+ "findings_not_source": non_source,
192
+ "findings_not_product": non_product,
193
+ "files": rows[:top_n],
194
+ "files_omitted": max(0, len(rows) - top_n),
195
+ "notes": notes,
196
+ }
197
+
198
+
199
+ def render_blast_text(blast: Optional[dict], *, top_n: int = 10) -> str:
200
+ """Render the blast-radius block as the text-format section."""
201
+ if not blast:
202
+ return ""
203
+ lines: list[str] = ["Migration blast radius (regression scope)"]
204
+ share = blast.get("request_surface_share")
205
+ if share is None:
206
+ lines.append(
207
+ f" {blast.get('endpoints_in_repo', 0)} endpoints in IR — "
208
+ "no request surface attributable; ranked by callers."
209
+ )
210
+ else:
211
+ lines.append(
212
+ f" {blast.get('endpoints_reached', 0)}/{blast.get('endpoints_in_repo', 0)} "
213
+ f"endpoints reached ({share * 100:.1f}% of the request surface)"
214
+ )
215
+ lines.append(
216
+ f" Files on a request path: {blast.get('files_on_request_path', 0)}"
217
+ f" of {blast.get('files_analyzed', 0)} analyzed"
218
+ + (f" [{len(blast.get('files_unresolved') or [])} unresolved]"
219
+ if blast.get("files_unresolved") else "")
220
+ )
221
+ for row in (blast.get("files") or [])[:top_n]:
222
+ lines.append(
223
+ f" {row['endpoints_reached']:>4} endpoints "
224
+ f"[{row['max_severity'].upper()}] {row['file']} "
225
+ f"({row['direct_callers']}+{row['indirect_callers']} callers)"
226
+ )
227
+ omitted = blast.get("files_omitted", 0) + max(0, len(blast.get("files") or []) - top_n)
228
+ if omitted:
229
+ lines.append(f" +{omitted} more file(s)")
230
+ for note in blast.get("notes") or []:
231
+ lines.append(f" note: {note}")
232
+ return "\n".join(lines)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes