sourcecode 3.4.0__tar.gz → 3.6.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.
Files changed (193) hide show
  1. {sourcecode-3.4.0 → sourcecode-3.6.0}/CHANGELOG.md +427 -1
  2. {sourcecode-3.4.0 → sourcecode-3.6.0}/PKG-INFO +4 -4
  3. {sourcecode-3.4.0 → sourcecode-3.6.0}/README.md +3 -3
  4. {sourcecode-3.4.0 → sourcecode-3.6.0}/pyproject.toml +1 -1
  5. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/archetype.py +4 -0
  7. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/cache_model.py +5 -4
  8. sourcecode-3.6.0/src/sourcecode/caller_metrics.py +168 -0
  9. sourcecode-3.6.0/src/sourcecode/caller_reach.py +205 -0
  10. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/cli.py +471 -301
  11. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/compare.py +76 -7
  12. sourcecode-3.6.0/src/sourcecode/degradation.py +373 -0
  13. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/explain.py +40 -14
  14. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/facts/registry.json +32 -0
  15. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/hibernate_strat.py +60 -7
  16. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/orchestrator.py +3 -0
  17. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/registry.py +3 -1
  18. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/server.py +4 -2
  19. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/migrate_check.py +330 -31
  20. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/migration_blast.py +5 -3
  21. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/output_budget.py +39 -0
  22. sourcecode-3.6.0/src/sourcecode/path_admission.py +153 -0
  23. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/posture.py +119 -27
  24. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/pr_comment_renderer.py +6 -1
  25. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/readiness_timeline.py +16 -1
  26. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/repository_ir.py +186 -57
  27. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps.py +9 -1
  28. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/ris.py +11 -0
  29. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/serializer.py +91 -8
  30. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_findings.py +38 -0
  31. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_impact.py +29 -163
  32. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_security_audit.py +6 -0
  33. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_tx_analyzer.py +6 -0
  34. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/verify_repo.py +22 -4
  35. sourcecode-3.4.0/src/sourcecode/caller_metrics.py +0 -61
  36. {sourcecode-3.4.0 → sourcecode-3.6.0}/.github/workflows/build-windows.yml +0 -0
  37. {sourcecode-3.4.0 → sourcecode-3.6.0}/.gitignore +0 -0
  38. {sourcecode-3.4.0 → sourcecode-3.6.0}/.ruff.toml +0 -0
  39. {sourcecode-3.4.0 → sourcecode-3.6.0}/CONTRIBUTING.md +0 -0
  40. {sourcecode-3.4.0 → sourcecode-3.6.0}/LICENSE +0 -0
  41. {sourcecode-3.4.0 → sourcecode-3.6.0}/SECURITY.md +0 -0
  42. {sourcecode-3.4.0 → sourcecode-3.6.0}/raw +0 -0
  43. {sourcecode-3.4.0 → sourcecode-3.6.0}/scripts/compare_integration_engines.py +0 -0
  44. {sourcecode-3.4.0 → sourcecode-3.6.0}/scripts/customer_smoke_test.sh +0 -0
  45. {sourcecode-3.4.0 → sourcecode-3.6.0}/scripts/generate_jdk_exports.py +0 -0
  46. {sourcecode-3.4.0 → sourcecode-3.6.0}/scripts/perf_harness.py +0 -0
  47. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/adaptive_scanner.py +0 -0
  48. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/architectural_baseline.py +0 -0
  49. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/architectural_delta.py +0 -0
  50. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/architecture_analyzer.py +0 -0
  51. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/architecture_summary.py +0 -0
  52. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/ast_extractor.py +0 -0
  53. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/cache.py +0 -0
  54. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/call_surface.py +0 -0
  55. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/canonical_ir.py +0 -0
  56. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/chain_rules.py +0 -0
  57. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/change_plan.py +0 -0
  58. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/cir_graphs.py +0 -0
  59. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/classifier.py +0 -0
  60. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  61. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/confidence_analyzer.py +0 -0
  62. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/constraint_diff.py +0 -0
  63. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/context_cache.py +0 -0
  64. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/context_graph.py +0 -0
  65. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/context_scorer.py +0 -0
  66. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/context_summarizer.py +0 -0
  67. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/contract_diff.py +0 -0
  68. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/contract_model.py +0 -0
  69. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/contract_pipeline.py +0 -0
  70. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/coverage_parser.py +0 -0
  71. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/dependency_analyzer.py +0 -0
  72. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/deployment_prefix.py +0 -0
  73. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/__init__.py +0 -0
  74. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/base.py +0 -0
  75. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  76. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/dart.py +0 -0
  77. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/dotnet.py +0 -0
  78. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/elixir.py +0 -0
  79. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/go.py +0 -0
  80. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/heuristic.py +0 -0
  81. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/hybrid.py +0 -0
  82. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/java.py +0 -0
  83. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  84. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/nodejs.py +0 -0
  85. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/parsers.py +0 -0
  86. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/php.py +0 -0
  87. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/project.py +0 -0
  88. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/python.py +0 -0
  89. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/ruby.py +0 -0
  90. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/rust.py +0 -0
  91. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/systems.py +0 -0
  92. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/terraform.py +0 -0
  93. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/detectors/tooling.py +0 -0
  94. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/doc_analyzer.py +0 -0
  95. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  96. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/endpoint_literals.py +0 -0
  97. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/endpoint_metrics.py +0 -0
  98. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  99. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/env_analyzer.py +0 -0
  100. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/envelope.py +0 -0
  101. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/error_schema.py +0 -0
  102. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/evidence_provider.py +0 -0
  103. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/facts/__init__.py +0 -0
  104. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/file_chunker.py +0 -0
  105. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/file_classifier.py +0 -0
  106. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/filter_surface.py +0 -0
  107. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/format_contract.py +0 -0
  108. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/fqn_utils.py +0 -0
  109. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/git_analyzer.py +0 -0
  110. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/graph_analyzer.py +0 -0
  111. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/graph_evidence.py +0 -0
  112. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/integration_coordinates.py +0 -0
  113. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/jdk_exports.py +0 -0
  114. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/license.py +0 -0
  115. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/__init__.py +0 -0
  116. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  117. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  118. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  119. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  120. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  121. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp/runner.py +0 -0
  122. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/mcp_nudge.py +0 -0
  123. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/metrics_analyzer.py +0 -0
  124. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/openapi_surface.py +0 -0
  125. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/parse_cache.py +0 -0
  126. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/path_filters.py +0 -0
  127. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/perf.py +0 -0
  128. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/pr_impact.py +0 -0
  129. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/prepare_context.py +0 -0
  130. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/progress.py +0 -0
  131. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/ranking_engine.py +0 -0
  132. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/reconciliation.py +0 -0
  133. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/redactor.py +0 -0
  134. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/reference_facts.py +0 -0
  135. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/relevance_scorer.py +0 -0
  136. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/rename_refactor.py +0 -0
  137. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/repo_classifier.py +0 -0
  138. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/__init__.py +0 -0
  139. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/context.py +0 -0
  140. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/errors.py +0 -0
  141. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/executor.py +0 -0
  142. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/planner.py +0 -0
  143. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/query.py +0 -0
  144. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/request.py +0 -0
  145. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/resolution.py +0 -0
  146. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/result.py +0 -0
  147. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/retriever.py +0 -0
  148. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/runtime.py +0 -0
  149. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  150. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  151. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  152. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  153. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  154. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  155. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/runtime_classifier.py +0 -0
  156. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/scanner.py +0 -0
  157. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/schema.py +0 -0
  158. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  159. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/security_config.py +0 -0
  160. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/security_posture.py +0 -0
  161. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/semantic_analyzer.py +0 -0
  162. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  163. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  164. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/semantic_services.py +0 -0
  165. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_event_topology.py +0 -0
  166. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_model.py +0 -0
  167. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_profiles.py +0 -0
  168. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_properties.py +0 -0
  169. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/spring_semantic.py +0 -0
  170. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/summarizer.py +0 -0
  171. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/__init__.py +0 -0
  172. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/config.py +0 -0
  173. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/consent.py +0 -0
  174. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/events.py +0 -0
  175. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/filters.py +0 -0
  176. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/telemetry/transport.py +0 -0
  177. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/test_sources.py +0 -0
  178. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/token_estimate.py +0 -0
  179. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/tree_utils.py +0 -0
  180. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/type_usage_surface.py +0 -0
  181. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/validation_inference.py +0 -0
  182. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/validation_surface.py +0 -0
  183. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/verify_edit.py +0 -0
  184. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/verify_rules.py +0 -0
  185. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/version_check.py +0 -0
  186. {sourcecode-3.4.0 → sourcecode-3.6.0}/src/sourcecode/workspace.py +0 -0
  187. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/functions/README.md +0 -0
  188. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/functions/get-license/index.ts +0 -0
  189. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  190. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/functions/telemetry/index.ts +0 -0
  191. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/sql/license_event_ordering.sql +0 -0
  192. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/sql/licensing_schema.sql +0 -0
  193. {sourcecode-3.4.0 → sourcecode-3.6.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,6 +1,432 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [3.6.0] — 2026-08-01
4
+
5
+ **The P0 block and the C1/C2 backlog.** Nine rows closed, three closed by measurement alone
6
+ (C1-17, C2-11, and C2-12 as stated did not reproduce). `migrate-check` stops billing work a
7
+ declared dependency already did, stops billing a Hibernate rewrite to another vendor's provider,
8
+ and stops assuming a single destination — `--target-jdk 11 --keep-boot` takes MIG-001 from 85 to 0
9
+ on openmrs-core.
10
+
11
+ Two patterns from 3.5.0 repeat, and both are worth carrying forward. **The reported symptom keeps
12
+ being narrower than the defect**: C1-10 was one rule and was also an evidence rule admitting JPA as
13
+ proof of Hibernate; C2-1 was a `why` trail and was also the ranking key underneath it. **And a
14
+ curated list beside real data keeps being the cause** — C2-3's trim schedule named sections the
15
+ payload does not have while the weight sat in two it never mentions, the third appearance of the
16
+ C4-1 shape in two releases.
17
+
18
+ **Minor, not patch**: blocking counts move (C1-10), reach-derived counts become nullable (C1-13),
19
+ `compare`'s ranking can move (C2-1), `migrate-check.findings` keeps one type across `--compact`
20
+ (C2-10), and two commands that wrote their confirmation to stderr now write it to stdout (C3-18).
21
+
22
+ ### Added
23
+
24
+ - **`migrate-check --target-jdk N` and `--keep-boot`** (C1-15, P0 gate 2). Asked about a JDK move,
25
+ the answer was `headline_blocker: boot3_migration` with fix hints sending a developer to migrate
26
+ 481 `javax.persistence` imports they never planned to touch; nothing could say "JDK 11, keep Boot
27
+ 2.7". The route is filtered over the `migration_target` vocabulary every rule already carried.
28
+ Measured on openmrs-core with `--target-jdk 11 --keep-boot`: **MIG-001 85 → 0**, blocking 114 → 0,
29
+ headline `hibernate_rewrite` → `none_applicable`. Narrowing is not silencing — `metadata.route`
30
+ publishes the target and every excluded axis, and a `limitations` entry states what was left out
31
+ and that it is unresolved, not fixed. **The default is unchanged**: no flags means every axis at
32
+ once.
33
+
34
+ - **`spring-audit` findings carry `first_line`** (C2-4), from the `SourceSpan` index the IR already
35
+ records. `null` where the symbol has no recorded span, never 0.
36
+
37
+ ### Changed
38
+
39
+ - **`migrate-check` reconciles findings with the build it already read** (C1-10, P0 gate 1,
40
+ **blocking counts move**). `MIG-021` fired as blocking while `javax.xml.bind:jaxb-api:2.3.1` sat
41
+ in the same `pom.xml`. A rule now declares the coordinates that satisfy it; a satisfied finding
42
+ gets `mitigated_by` and `code_context: "dependency_satisfied"`, leaving `blocking_count` exactly
43
+ as test scope does, and its hint stops saying "add this dependency". `satisfied_by` is empty for
44
+ every rule whose remedy is a source change.
45
+ - **The Hibernate axis now needs Hibernate evidence.** It billed 45.6 days to a repository whose
46
+ declared provider was EclipseLink because a bare `javax.persistence` import counted as proof —
47
+ that proves JPA, not Hibernate. A declared competing provider refutes the inference, and only
48
+ the inference: a repository carrying both coordinates keeps direct evidence. An inference from
49
+ a JPA import alone now reports `medium`, not `high`.
50
+ - Battery: `blocking_count` identical on all six repositories.
51
+
52
+ - **A capped blast radius answers `unknown`, not `0`** (C1-13, **reach-derived counts become
53
+ nullable**). A hub capped at `depth=1` reported `endpoints_affected_count: 0` for a walk that
54
+ never looked. Under the cap a reach-derived zero becomes `null`, named in
55
+ `stats.unknown_under_cap`. A **non-zero** count under the cap stays a number — it is a floor — and
56
+ an **uncapped** run keeps its measured zero.
57
+
58
+ - **`compare` ranks on measurements, not on rendered rows** (C1-13/C2-1, **ranking may move**).
59
+ Three of the seven cost fields were `len()` of a display list, so the ordering was computed over
60
+ however many rows the renderer had decided to show. Capped magnitudes are declared in
61
+ `cost_floor_fields` beside `cost`, never inside it.
62
+
63
+ - **`impact.endpoints_affected[]` gains `controller_class` and `handler_symbol`** (C2-12), the names
64
+ `impact-chain` already uses. Grouping by controller needed a different key per command on payloads
65
+ one run produces. Original keys kept as aliases.
66
+
67
+ - **`effort_breakdown.affected_files` is now `blocking_affected_files`** (C1-14, alias kept). It
68
+ named a different population from `summary.affected_files` — 264 against 265 on openmrs-core — and
69
+ the shared name was the whole of the contradiction. Both carry a `_basis`.
70
+
71
+ ### Fixed
72
+
73
+ - **The payload budget cuts where the bytes are** (C2-3). `_TRIM_SCHEDULE` is a hand-written list of
74
+ key names: on a 1 222-file `review-pr` diff it trimmed 1 733 KB → 1 732 KB against a 97 KB budget,
75
+ walking to `execution_paths` while every byte sat in `changed_files` and `runtime_changes`, which
76
+ it does not mention. Once the schedule is exhausted the remaining cut is chosen by measuring which
77
+ list is largest. Measured on that payload shape: **566 KB → 71 KB**.
78
+
79
+ - **`ask verify` no longer passes with zero contracts declared** (C3-15, **behaviour change for
80
+ pipelines**). It returned `pass` + exit 0 on a repository that declared nothing — a green light
81
+ for having verified nothing. `evaluate_repo` returns before it even builds the CIR when no
82
+ contracts exist, so no code was read and no contract was checked: the same standing as a
83
+ repository that could not be analysed, and now reported the same way — **`unverified`, exit 2**,
84
+ with the reason stated. Exit 2 and not 1 because the published contract reserves 1 for
85
+ violations found and 2 for a run that could not verify. Asserted under every `--fail-on` choice:
86
+ `never` suppresses violations from blocking, it does not turn a run that verified nothing into a
87
+ verified one. This is C3-22 in different clothes — there a typo in `$DIR` made the gate
88
+ permanently green, here a repository that never adopted contracts did.
89
+
90
+ - **A command that writes its answer to a file now confirms on stdout, not stderr** (C3-18,
91
+ **behaviour change for scripts reading stderr**). `repo-ir`, `modernize`, `export` and
92
+ `cold-start` wrote the "written to" confirmation to stderr while the payload went to the file
93
+ and stdout stayed empty, so `ask repo-ir -o ir.json > log.txt` captured **nothing** and
94
+ PowerShell 5.1 raised `NativeCommandError` over a run that succeeded. Measured on
95
+ spring-petclinic before the fix: stdout 0 bytes, stderr 155. C3-26 set this rule in 3.5.0 for
96
+ `cache warm`/`cache clear`; these four were missed because the stderr battery listed its
97
+ commands **by hand**. The `--output` rows are now discovered from the CLI.
98
+
99
+ - **Usage errors emit the JSON envelope for a pipeline** (C3-4). The interception was patched onto
100
+ `click.UsageError.show`, which Typer never calls when `rich_markup_mode` is set — it is, for the
101
+ help panels — so **no usage error had been emitting an envelope at all** and `ask <cmd>
102
+ --badflag` answered automation with a rich text box. Split by audience: a pipeline gets the
103
+ envelope, a person at a terminal keeps the `Usage:` line and the pointer to `--help`.
104
+
105
+ - **A flag no longer changes the type of a key** (C2-10, **breaking for `--compact` consumers**).
106
+ `migrate-check.findings` was a bare array in full output and a
107
+ `{total, shown, omitted, items}` object under `--compact` — the same key, two types. A consumer
108
+ iterating `findings` broke silently on a flag it did not pass: nothing errored, the loop simply
109
+ ran over the four keys of a summary object instead of over the findings.
110
+ - `findings` is a **list in both modes**. The counts moved to a `findings_cap` sibling that
111
+ exists **only when a cap actually bit**, and it carries the I-8 `display_list` statement of
112
+ what the cut changed and what stayed exact.
113
+ - **Five collections had it, not one**: top-level `findings`, `hibernate.rewrite_targets`,
114
+ `hibernate.findings`, `hibernate.critical_call_chains`, `hibernate.golden_sql_hotspots`, and
115
+ `blast_radius.files`.
116
+ - **Half of C2-13 falls out of the same fix.** `migrate-check.findings` is now the bare array
117
+ `spring-audit.findings` always was — one word, one type, one parser, measured across all
118
+ three payloads. The `schema_version` fragmentation is the half that stays open.
119
+ - Enforced rather than documented: `tests/test_flag_shape_stability.py` walks both payloads key
120
+ by key and fails on any type that moves. The same walk covers the root command's `--compact`
121
+ and `--agent`, which were **measured clean** while fixing this and are locked so they stay
122
+ clean.
123
+
124
+ - **A misplaced global flag is told where that flag lives** (C3-4). `ask spring-audit --full`
125
+ answered "No such option: --full" about a flag the tool does have — it is declared on the root
126
+ command. The flag is looked up on the root command, so every global flag typed after every
127
+ subcommand is covered and no flag name is written into the check; a genuinely unknown flag earns
128
+ no hint, asserted.
129
+
130
+ - **`repo-ir` states the output cost before it is paid** (C3-18). The advisory ran after the IR was
131
+ built, serialized and written — the whole bill settled. It now runs off the file count before the
132
+ build starts, as a **band** (10.8–27.9 KB per Java file) rather than a point: the per-file cost
133
+ spreads 2.6× across sampled repositories with how densely the code cross-references, and
134
+ publishing a midpoint as one figure is the C1-12 defect. The band is asserted to bracket what
135
+ those repositories actually produced.
136
+
137
+ - **A capped `file_relevance` list says what was cut, measured** (C3-20). The hint published the
138
+ *budget* as the number of files shown — on spring-petclinic, "Showing top 40/176" beside a list
139
+ of **25** — fired even when nothing had been cut, and advised `--full` while `--full` was active.
140
+ Two constraints trim the list (a visibility threshold and a per-category cap of half the budget)
141
+ and the sentence knew about neither. Now: nothing unless a cap really bit, the figure is what was
142
+ shown against the population it samples, a flag is named only when it would change the answer,
143
+ and the I-8 `display_list` block sits beside it.
144
+
145
+ - **`--no-cache` on a subcommand says what it does** (C3-5). Six commands claimed "always reads
146
+ fresh source (no snapshot cache)" while `ask cache model` records them reading `ris`, `cir` and
147
+ `parse`. One string now, stating the reason it is a no-op: since C1-9 every layer keys on the
148
+ exact tree state and the parse layer is content-addressed, so there is no staleness to bypass.
149
+ The flag stays accepted so scripts do not break, and is real on `ask --no-cache` (root).
150
+
151
+ - **`--output <file>` now returns the whole answer, which is what its own note promised**
152
+ (C2-2, first M8 increment). Measured on `org.broadleafcommerce.common.money.Money`: the file
153
+ held **30 of 134** caller classes and **50 of 1 302** indirect callers — the same sample stdout
154
+ gets — while the payload told the reader *"use `--output` for the full list"*.
155
+ - **The recorded cause was wrong, and fixing what was recorded would not have worked.** The
156
+ file carried no `_budget_note` at all: the caps were applied at *construction*, inside
157
+ `compute_blast_radius`, before any output flag was consulted. No option could have returned
158
+ the full list.
159
+ - A cap is now a **rendering** parameter (`list_limit`) and never a measurement one — every
160
+ `stats.*_count` is taken over the full population either way (ADR-0008 R5-R8). `--output`
161
+ renders unbounded; stdout keeps the sample and names the total; and the note only claims a
162
+ cap that actually bit, with the I-8 `display_list` effect block beside it.
163
+ - The output budget is skipped for a file as well. `--agent` and `--compact` already did this;
164
+ `impact` was the one that did not.
165
+ - Same rule for `impact-chain`, with one qualification: an **explicit `--limit` still wins**.
166
+ A default is not a request; a typed flag is (`_flag_was_given`, off Click's parameter source).
167
+ - The USER_GUIDE now states the rule where a reader meets it, beside the `impact` output.
168
+
169
+ ## [3.5.0] — 2026-07-31
170
+
171
+ **M7.7 "Robustness floor" — the axis field evaluation #5 scored 4/10.** Ten pieces, and the
172
+ pattern in eight of them is the same: the reported symptom was real, and the measured cause was
173
+ not the one anyone had assumed. Input admission had four behaviours for one fact, not one broken
174
+ command. `posture`'s cost was not the Java parse but a quadratic scan of two branches that could
175
+ not fire — and that dead code was also a false zero on the security axis. The blast radius
176
+ disagreed with itself because there were two traversals of the reverse graph, not because reach
177
+ is hard. Two reported symptoms did not reproduce at all and are recorded as measured rather than
178
+ fixed blind.
179
+
180
+ **Minor, not patch**, on two counts: `impact` now publishes endpoints, transaction boundaries and
181
+ a security surface where it published `0`, so its risk score moves and a pipeline gating on it can
182
+ change colour; and `estimated_effort_days` is nullable where a range applies.
183
+
184
+ ### Changed
185
+
186
+ - **`retrieve` moves from `experimental` to `parked`** (C3-24) — kept working, no longer
187
+ developed, and saying so: `[PARKED]` in its own help, in the README table and in the user
188
+ guide, all rendered from `cli.COMMAND_TIERS`. The battery now also asserts that a parked
189
+ command declares itself, the twin of the assertion that already existed for experimental.
190
+ **Two of the three reported symptoms did not reproduce**, and are recorded that way rather
191
+ than repeated: `transaction-propagation` returns one entity because the battery repository
192
+ has exactly one non-default boundary (`REQUIRES_NEW: 1` of 161) — `observations.total` is the
193
+ tx-index population standing beside the answer, not a truncated total, and a `--limit` exists
194
+ (default unbounded) that records `entities_truncated` when it bites; `security-surface`
195
+ returns 0 where `spring-audit --scope security` also returns 0. What does reproduce is an
196
+ `explanation` block shaped for a ranking that never ran. The surface is not non-functional —
197
+ it is undifferentiated, re-stating behind 15+ intents what the core commands answer better.
198
+
199
+ ### Measured
200
+
201
+ - **`review-pr`'s path argument is not decorative — the reported scope defect does not
202
+ reproduce** (C3-27, scope half). Field evaluation #5 reported that given a submodule path the
203
+ command reports the *enclosing* repository as `repo_root` and reviews 227 files from other
204
+ modules. Re-measured on six shapes: a module subdirectory, that same subdirectory as the
205
+ working directory, a nested path given relative to the root, an embedded repository, a **real
206
+ git submodule** (where `.git` is a gitlink *file*, so an `exists()` test is true for it and the
207
+ walk up has to stop there), and a submodule asked for a ref only its parent has. Every run is
208
+ bounded to the path it was given: on BroadleafCommerce the scoped runs return exactly
209
+ `git diff --name-only <ref> -- <scope>` — 5 of 17 files at `core/`, 855 at
210
+ `core/broadleaf-framework/`, 1 222 at `core/` over a 3 332-file range — `_meta.scope` names the
211
+ tree answered about, no sibling-module path appears anywhere in the payload, and the
212
+ missing-ref case is refused (`git_ref_not_found`, exit 1) instead of borrowed from the parent.
213
+ Recorded as not reproducing rather than fixed blind, and the six shapes are now asserted in
214
+ `tests/test_git_scope_namespace.py` so the symptom cannot return silently. The probable closer
215
+ is the Bug A scope-namespace seam, which landed between the evaluated build and this
216
+ measurement.
217
+
218
+ ### Fixed
219
+
220
+ - **`impact` reported `endpoints_affected_count: 0` for a symbol `impact-chain` reached 21
221
+ endpoints from** (C1-16, endpoint half — the caller half closed in 3.5.0). A false zero on the
222
+ blast-radius axis, which is the number a user takes a risk decision with. The cause was
223
+ measured, not guessed: there were **two traversals** of the reverse graph, and they disagreed
224
+ twice.
225
+ - `impact` looked up the exact reverse-graph key. The callers of a class are stored under its
226
+ **method** keys (`Foo#doWork`), never under `Foo`, so the walk terminated at every
227
+ class-level node — which is exactly what a DI `injects` edge normalises to. 125 indirect
228
+ callers instead of 1 442.
229
+ - It admitted `implements`/`extends` as caller edges (an implementor does not call an
230
+ interface by implementing it, CH-006) under a comment claiming its edge set was *"consistent
231
+ with `spring_impact._SKIP_EDGE_TYPES`"*, which skips both. And it held no interface index, so
232
+ it never crossed the DI boundary at all: on `Money` it reached 200 classes and **zero
233
+ controllers**, against 441 and 18.
234
+ - New authority `sourcecode/caller_reach.py`, registered as fact `blast_radius_reach`, with
235
+ `impact`, `impact-chain` and `migrate-check --blast-radius` all bound to it. On
236
+ BroadleafCommerce's `Money`, `impact` now reports **20 of the 21 endpoints** (0 before),
237
+ **53 transactional boundaries** (0), **20 persistence paths** (2) and **15 security-gated
238
+ endpoints** (0) — while `stats.direct_caller_count` stays **134**, so the caller axis closed
239
+ in 3.5.0 does not move. Parity verified command-to-command on spring-petclinic
240
+ (`OwnerRepository`: 13 = 13 endpoints, exact) and open-banking-gateway (two symbols, exact):
241
+ the reconciliation adds no false positives. Cost unchanged (5.0 s vs 4.9 s on that repo).
242
+ - Also fixed here: the explanation prose read *"433 direct callers"* beside a payload
243
+ publishing 134 caller classes and 433 reference sites, each named for its unit. Prose is
244
+ what a reader quotes, so it now names its unit too.
245
+ - **Residual, stated because it is measured:** one endpoint of the 21
246
+ (`BroadleafOauthRegisterController#processRegister`) is still reached by `impact-chain` and
247
+ not by `impact`, because the two commands read *different graphs* — `impact` builds the repo
248
+ IR, `impact-chain` the CIR, and the CIR carries a method key for that handler the repo IR
249
+ does not. That is a graph-population gap, not a traversal one. Seed expansion is asymmetric
250
+ for the same reason (`impact-chain` expands a seed to its subtypes, `impact` only to the
251
+ interfaces it implements).
252
+
253
+ - **`posture` costs a sixth of what it did, and a warm is finally worth something to it**
254
+ (C3-25) — 24.5 s → **10.1 s** cold and 19.3 s → **1.6 s** after `cache warm`, measured on
255
+ BroadleafCommerce in isolation. Field evaluation #5 measured 195 s, ×18 the session median,
256
+ on the one command the product differentiates on, and the cache model honestly documented
257
+ that a warm bought it almost nothing. Both had the same cause, and it was not the Java parse
258
+ everyone blamed:
259
+ - **The security predicate re-walked every node in the repository for every class** —
260
+ 6 075 × ~29 000 ≈ 176 M dictionary reads on the battery repository, larger than the parse
261
+ itself — looking for `node["return_type"]` and `node["supertypes"]`. **The IR publishes
262
+ neither key on a node.** Inheritance and return types are `extends`, `implements` and
263
+ `returns` *edges*, so both branches were structurally dead in every release: a repository
264
+ that wires its chain with a `@Bean SecurityFilterChain` or a filter subclass and no
265
+ `@EnableWebSecurity` was reported as configuring no security at all — a **false zero on the
266
+ axis this command exists for**. The predicate now reads the edges, once per IR. On
267
+ BroadleafCommerce it recovers 6 request-chain configurations, verified 6/6 against source
268
+ with no false positive, one of which is `@ConditionalOnProperty` and now correctly qualifies
269
+ the endpoint claim as unresolved instead of silently strengthening it.
270
+ - **`posture` built its own CIR on every run** — the same parse `cache warm`, `explain` and
271
+ `impact-chain` already share. It now goes through the shared knowledge cache, poison-safe by
272
+ the `peek_cir` rule: the knowledge key is repo-wide and carries no scope, so a run bounded to
273
+ a subdirectory neither reads nor writes it and keeps its own unshared build.
274
+ - **`--diff` parsed the repository once per side.** The IR does not depend on which profiles
275
+ are asked about — only the resolution over it does — so the second side now costs the
276
+ resolution alone: 2.3 s warm for a two-set comparison, the shape a per-PR gate runs.
277
+
278
+ - **A degraded run now states the effect of the degradation** — invariant **I-8**, accepted and
279
+ enforced (C3-27, and the class field evaluation #5 named). `review-pr` published
280
+ `analysis_limiter: {"missing_signals": ["dependency_graph", "import_graph"]}`: what was
281
+ absent, and nothing about what its absence changed. A reader could not tell whether the answer
282
+ was a floor, a ceiling or unaffected — the same defect shape as `statically_unreferenced: 0`,
283
+ one level up.
284
+ - Every signal a command can report as missing now carries **what it feeds**, **what the
285
+ answer does without it**, and a **`direction`**: `under_reports` (read the answer as a
286
+ floor — absence of a finding is not evidence of none), `over_reports` (a ceiling), or
287
+ `undetermined`. Mixed directions collapse to `undetermined`: an answer that is a floor in
288
+ one place and a ceiling in another is neither. The block ends with one sentence on how to
289
+ read the result, and the GitHub comment prints it beside the `OMITTED` badge — a reviewer
290
+ reads the comment, not the JSON.
291
+ - One authority (`sourcecode/degradation.py`) serves every emitter, and the battery discovers
292
+ signal names **from the source by AST**, so a signal added without an effect statement fails
293
+ the suite. That scan found a second emitter the field never reached: **`archetype`** declares
294
+ `repo_ir_subsystem_masses` and `semantic_ir_spi_fanin` missing on *every* run — signals wired
295
+ into no release — and its missing `endpoints` signal enters the score as `0.0` rather than as
296
+ unknown, so an HTTP-facing repository is scored as if it exposed no HTTP surface. Both now
297
+ say so in the payload; correcting the score itself is separate work.
298
+ - **The second family is now covered too: what the product cuts on purpose.** A missing signal
299
+ is something the run did not have; a **cap** is something it had and chose not to publish.
300
+ Those said only *that* they capped — `"_truncation_summary": {"total_omitted_items": 1442}`,
301
+ `"bfs_truncation_reason": "hub_class_depth_cap"`, `"is_stale": true` — and a count of omitted
302
+ items is not an effect. `CAP_EFFECTS` gives each one the same three fields plus a fourth,
303
+ `exact`, which is what separates a cap from a missing signal: what survived the cut.
304
+ - **The output budget** publishes the least intuitive effect of all: *nothing moved*. Every
305
+ figure is computed before serialisation, so the counts stay exact and only the lists became
306
+ samples — and deriving a count from a truncated list is exactly how the field arrived at
307
+ four contradictory fan-in figures. The block now says so, and the battery asserts a trimmed
308
+ payload keeps its count.
309
+ - **Display caps** (`impact-chain --limit`, `impact`'s 30-class sample, `retrieve --limit`)
310
+ state that the total beside the list was computed over the whole population, so list and
311
+ total disagreeing is construction, not contradiction.
312
+ - **The hub-class depth cap** names every figure it turns into a floor — indirect callers and
313
+ the endpoints, transaction boundaries, security surface and risk score derived from them —
314
+ and every figure it leaves exact. A zero under a cap means the capped walk found none.
315
+ - **A stale snapshot** is `undetermined`, not a floor: it describes a different tree, so a
316
+ symbol added since is missing *and* a symbol deleted since is still listed.
317
+ - **An unavailable section** (`migrate-check --blast-radius` when the IR cannot be built)
318
+ says the failure is bounded to that block and the findings beside it are untouched.
319
+ - Same enforcement as the signal family: the battery discovers cap names from the source by
320
+ AST, so a cap applied without an effect statement fails the suite.
321
+
322
+ - **A run that succeeded now writes nothing to stderr** (C3-26). PowerShell 5.1 — a declared
323
+ target platform — turns any stderr write from a native command into a `NativeCommandError`,
324
+ so an informational line makes a successful run look like a failed one and forces the caller
325
+ to swallow errors it should be reporting. Measured non-TTY before fixing: `migrate-check`
326
+ wrote a size advisory, `validation` a note about an empty result, `explain` a cache hit/miss
327
+ line; everything else was already clean. Those three now go through one seam (`cli._notice`)
328
+ that speaks only when stderr is a terminal. No `--quiet` flag: a flag has to be remembered by
329
+ every caller, on the platform where forgetting it is fatal.
330
+ - **`cache warm` and `cache clear` print their status on stdout**, where `cache status` in the
331
+ same group already printed it. Their status *is* their answer — on stderr, `ask cache warm >
332
+ log.txt` captured nothing at all.
333
+ - Errors keep stderr, where they pair with a non-zero exit; `tests/test_stderr_purity_matrix.py`
334
+ asserts both halves across 13 commands through a real subprocess, because a CliRunner blurs
335
+ the two streams — which is how three of these survived the stdout-purity battery that
336
+ already existed.
337
+
338
+ - **Every example the CLI prints now parses against the CLI that prints it** (C3-16). The root
339
+ help led its gating panel with `pr-impact . --since main`; `pr-impact` has no `--since`. It
340
+ was reported by three field evaluations and survived all three, because the check was a
341
+ person re-reading a hand-written block — and the header panels print invocations *without*
342
+ the `ask ` prefix, so even grepping for `ask ` missed it. The example is now
343
+ `pr-impact . --files -` (the stdin form, verified against a real `git diff --name-only`), and
344
+ `tests/test_help_examples.py` extracts all 156 invocations from every help screen — root
345
+ panels, command docstrings, group and subcommand help — and parses each one against the
346
+ command it names. The battery states its own scope: the command and its options exist and
347
+ take the arity given; values are not validated and nothing is executed.
348
+
349
+ - **One class, four fan-in figures — two of them wrong about their own unit** (C1-16, field
350
+ evaluation #5). On `org.broadleafcommerce.common.money.Money` the product published 1 868,
351
+ 673, 418 and 151 under names that all read as fan-in, and the note written to pre-empt that
352
+ confusion promised an ordering (`modernize.in_degree` *"always the largest"*, the other two
353
+ within *"a small margin"*) that measurement falsified by 2,8× and 177 %. This is the number
354
+ a risk decision is taken with: *"¿el blast radius son 484 o 611 clases?"*
355
+ - **`impact.stats.direct_caller_count` counted caller *symbols*** — 418, of which 346 were
356
+ method-level, so a class was counted once per method touching the target. It now counts
357
+ **distinct classes** (134) with the reference sites beside it as
358
+ **`direct_caller_symbol_count`** (418). The risk score still weighs reference sites; it was
359
+ the published unit that was false. `direct_callers` is a class list (`direct_caller_symbols`
360
+ keeps the sites), so the array and its count are finally the same unit.
361
+ - **`explain.incoming_callers` deduplicated by simple name**, collapsing 186 distinct classes
362
+ into 151 and dropping 35 real dependents. Identity is the FQN; a colliding simple name is
363
+ rendered qualified. New **`incoming_callers_count`** is measured before the `--limit` cap,
364
+ so a consumer never has to take `len()` of a truncated array.
365
+ - **`impact-chain.metadata.callers_total` was the size of the whole chain**, not fan-in, and
366
+ was absent from the note. It stays (removing a published key is breaking) and now says so,
367
+ beside **`chain_classes_total`** (441) and **`direct_caller_count`** (134) — which equals
368
+ `impact`'s figure by construction. `explain` is a declared superset: it admits import-only
369
+ references, a blast radius does not.
370
+ - **The note is generated from a registry** (`caller_metrics.FAN_IN_FIGURES`), each figure
371
+ carrying its unit and the references it admits, and the battery asserts every figure appears
372
+ with its unit. No ordering between units is promised any more — the three falsified phrases
373
+ are banned by test. Registered as fact `caller_fan_in` (ADR-0008 R11-R14).
374
+ - `compare`'s evidence trail read `len()` of the 30-item display array and told every hub
375
+ class in the repository it had "30 direct callers". It reads `stats` now.
376
+ - Still open and stated rather than guessed: `impact` reports `endpoints_affected_count: 0`
377
+ where `impact-chain` returns 21 endpoints for the same class. Measured today, the two
378
+ traversals reach different populations (259 vs 441 classes at full depth), so that half is
379
+ engine reach, not presentation.
380
+
381
+ - **A command must not answer a question about a repository it could not read** (C3-22, C3-23,
382
+ the robustness finding of field evaluation #5 — *"un gate que puede pasar sin analizar nada
383
+ es peor que no tener gate, porque genera confianza injustificada"*). The same nonexistent
384
+ path produced four different behaviours: six commands refused it correctly, `verify`
385
+ answered `{"verdict": "pass", "exit_code": 0}`, `posture` answered an all-zeros payload with
386
+ exit 0, `archetype` raised a `FileNotFoundError` traceback through the JSON-always contract,
387
+ and `cold-start` reported `no_ris`. In `ask verify $DIR --fail-on new`, one typo in the
388
+ variable was a permanently green pipeline.
389
+ - **One admission authority** (`sourcecode.path_admission`): "can this path be analysed?" is
390
+ one fact, so **25 path-taking commands** now derive it in one place instead of twenty
391
+ inline copies plus four omissions. The refusal carries a machine-readable **`reason`** —
392
+ `missing`, `not_a_directory`, or `unreadable`.
393
+ - **`unreadable` is new and was not in the field report.** A directory that exists but cannot
394
+ be listed produced the same `archetype` traceback; it is now refused before the scan starts.
395
+ - **`verify` and `verify-edit` refuse with exit 2, not 1.** Their published contract reserves
396
+ 2 for *unverified* — the run that could not look — as distinct from 1, the run that looked
397
+ and found violations. A gate that never analysed anything reports neither `pass` nor a
398
+ violation count.
399
+ - **`impact-chain` and `impact` name the repository they actually read.** A path in the
400
+ symbol slot made them report `not_found` about the current working directory. A file path
401
+ is still a legitimate symbol; a symbol that *is* a directory, or that carries a path
402
+ separator and does not exist, now says which repository was analysed and how the arguments
403
+ are ordered.
404
+ - The battery discovers path-taking commands from the CLI itself: a new command that skips
405
+ the gate fails the suite.
406
+
407
+ - **An effort figure is a point only when nothing under it is a range** (C1-12, the third
408
+ reconciliation gate of field evaluation #4). `migrate-check` folded the Hibernate 5→6 slice
409
+ into the headline as the **midpoint** of a range the same report labels `confidence: low`,
410
+ and that the Hibernate effort model itself calls a non-deduplicated *upper bound*. Measured
411
+ on the battery: BroadleafCommerce billed **157.7 d, 153.7 of them (97%) from 76.5–230.8**;
412
+ openmrs-core 76.4 d with 62.2 (81%) from 28.9–95.6. The arithmetic was right and the claim
413
+ was still false — a 3× span presented as one number.
414
+ - New top-level **`estimated_effort_range_days`** `{low, high, confidence, basis}`. The band
415
+ is the worst confidence of the contributing slices; findings effort is a per-file
416
+ measurement and enters as a point.
417
+ - **`estimated_effort_days` is `null`** when a range slice is in the total (never `0` — that
418
+ reads as "no work"), and stays a number on every repo where no range applies. The text
419
+ report prints the span: `Estimated effort: 80.6–234.9d (low confidence — not a point
420
+ estimate)`.
421
+ - `effort_breakdown.hibernate_rewrite_effort_days` is `null`: the midpoint is not a
422
+ measurement of anything, so it is no longer published. The range sits beside it in
423
+ `hibernate_rewrite_range`, with `hibernate_rewrite_effort_basis` and `unestimated_slices`.
424
+ - **An inferred version buys no number.** When the 5→6 axis applies only because a Spring
425
+ Boot 2 BOM manages Hibernate 5.x — no version declared in the build — the rewrite *scope*
426
+ is a hypothesis: the slice contributes neither a point nor a range, and the gap is named.
427
+ The readiness dimension stays applicable; not estimating is not pretending it is absent.
428
+ - Readiness snapshots/trends carry `estimated_effort_low_days` / `estimated_effort_high_days`
429
+ so the series does not go blind exactly where the effort is largest.
4
430
 
5
431
  ## [3.4.0] — 2026-07-29
6
432
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 3.4.0
3
+ Version: 3.6.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` · `retrieve` |
224
- | **parked** | kept working, no longer developed | none today |
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` | experimental | typed knowledge queries over the model | |
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` · `retrieve` |
186
- | **parked** | kept working, no longer developed | none today |
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` | experimental | typed knowledge queries over the model | |
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 |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "3.4.0"
7
+ version = "3.6.0"
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.4.0"
7
+ __version__ = "3.6.0"
@@ -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",), "shared", False,
129
- "Resolves the conditional bean graph on every run; a warm saves it the Java parse, "
130
- "which is the smaller half of its work.",
131
- "24.5 s 19.3 s"),
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.",