sourcecode 3.3.0__tar.gz → 3.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. {sourcecode-3.3.0 → sourcecode-3.5.0}/CHANGELOG.md +353 -1
  2. {sourcecode-3.3.0 → sourcecode-3.5.0}/PKG-INFO +11 -8
  3. {sourcecode-3.3.0 → sourcecode-3.5.0}/README.md +10 -7
  4. {sourcecode-3.3.0 → sourcecode-3.5.0}/pyproject.toml +1 -1
  5. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/archetype.py +4 -0
  7. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/cache_model.py +5 -4
  8. sourcecode-3.5.0/src/sourcecode/caller_metrics.py +168 -0
  9. sourcecode-3.5.0/src/sourcecode/caller_reach.py +205 -0
  10. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/cli.py +231 -270
  11. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/compare.py +6 -2
  12. sourcecode-3.5.0/src/sourcecode/degradation.py +373 -0
  13. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/explain.py +40 -14
  14. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/facts/registry.json +32 -0
  15. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/orchestrator.py +3 -0
  16. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/registry.py +3 -1
  17. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/server.py +4 -2
  18. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/migrate_check.py +171 -15
  19. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/migration_blast.py +5 -3
  20. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/output_budget.py +11 -0
  21. sourcecode-3.5.0/src/sourcecode/path_admission.py +153 -0
  22. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/posture.py +119 -27
  23. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/pr_comment_renderer.py +6 -1
  24. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/readiness_timeline.py +16 -1
  25. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/repository_ir.py +96 -48
  26. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps.py +9 -1
  27. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/ris.py +11 -0
  28. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/serializer.py +10 -0
  29. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_impact.py +29 -163
  30. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/verify_edit.py +190 -7
  31. sourcecode-3.3.0/src/sourcecode/caller_metrics.py +0 -61
  32. {sourcecode-3.3.0 → sourcecode-3.5.0}/.github/workflows/build-windows.yml +0 -0
  33. {sourcecode-3.3.0 → sourcecode-3.5.0}/.gitignore +0 -0
  34. {sourcecode-3.3.0 → sourcecode-3.5.0}/.ruff.toml +0 -0
  35. {sourcecode-3.3.0 → sourcecode-3.5.0}/CONTRIBUTING.md +0 -0
  36. {sourcecode-3.3.0 → sourcecode-3.5.0}/LICENSE +0 -0
  37. {sourcecode-3.3.0 → sourcecode-3.5.0}/SECURITY.md +0 -0
  38. {sourcecode-3.3.0 → sourcecode-3.5.0}/raw +0 -0
  39. {sourcecode-3.3.0 → sourcecode-3.5.0}/scripts/compare_integration_engines.py +0 -0
  40. {sourcecode-3.3.0 → sourcecode-3.5.0}/scripts/customer_smoke_test.sh +0 -0
  41. {sourcecode-3.3.0 → sourcecode-3.5.0}/scripts/generate_jdk_exports.py +0 -0
  42. {sourcecode-3.3.0 → sourcecode-3.5.0}/scripts/perf_harness.py +0 -0
  43. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/adaptive_scanner.py +0 -0
  44. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/architectural_baseline.py +0 -0
  45. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/architectural_delta.py +0 -0
  46. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/architecture_analyzer.py +0 -0
  47. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/architecture_summary.py +0 -0
  48. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/ast_extractor.py +0 -0
  49. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/cache.py +0 -0
  50. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/call_surface.py +0 -0
  51. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/canonical_ir.py +0 -0
  52. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/chain_rules.py +0 -0
  53. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/change_plan.py +0 -0
  54. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/cir_graphs.py +0 -0
  55. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/classifier.py +0 -0
  56. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  57. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/confidence_analyzer.py +0 -0
  58. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/constraint_diff.py +0 -0
  59. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/context_cache.py +0 -0
  60. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/context_graph.py +0 -0
  61. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/context_scorer.py +0 -0
  62. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/context_summarizer.py +0 -0
  63. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/contract_diff.py +0 -0
  64. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/contract_model.py +0 -0
  65. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/contract_pipeline.py +0 -0
  66. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/coverage_parser.py +0 -0
  67. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/dependency_analyzer.py +0 -0
  68. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/deployment_prefix.py +0 -0
  69. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/__init__.py +0 -0
  70. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/base.py +0 -0
  71. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  72. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/dart.py +0 -0
  73. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/dotnet.py +0 -0
  74. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/elixir.py +0 -0
  75. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/go.py +0 -0
  76. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/heuristic.py +0 -0
  77. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/hybrid.py +0 -0
  78. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/java.py +0 -0
  79. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  80. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/nodejs.py +0 -0
  81. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/parsers.py +0 -0
  82. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/php.py +0 -0
  83. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/project.py +0 -0
  84. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/python.py +0 -0
  85. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/ruby.py +0 -0
  86. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/rust.py +0 -0
  87. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/systems.py +0 -0
  88. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/terraform.py +0 -0
  89. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/detectors/tooling.py +0 -0
  90. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/doc_analyzer.py +0 -0
  91. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/dynamic_argument_surface.py +0 -0
  92. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/endpoint_literals.py +0 -0
  93. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/endpoint_metrics.py +0 -0
  94. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/entrypoint_classifier.py +0 -0
  95. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/env_analyzer.py +0 -0
  96. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/envelope.py +0 -0
  97. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/error_schema.py +0 -0
  98. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/evidence_provider.py +0 -0
  99. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/facts/__init__.py +0 -0
  100. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/file_chunker.py +0 -0
  101. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/file_classifier.py +0 -0
  102. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/filter_surface.py +0 -0
  103. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/format_contract.py +0 -0
  104. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/fqn_utils.py +0 -0
  105. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/git_analyzer.py +0 -0
  106. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/graph_analyzer.py +0 -0
  107. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/graph_evidence.py +0 -0
  108. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/hibernate_strat.py +0 -0
  109. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/integration_coordinates.py +0 -0
  110. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/jdk_exports.py +0 -0
  111. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/license.py +0 -0
  112. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/__init__.py +0 -0
  113. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  114. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  115. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  116. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  117. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  118. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp/runner.py +0 -0
  119. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/mcp_nudge.py +0 -0
  120. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/metrics_analyzer.py +0 -0
  121. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/openapi_surface.py +0 -0
  122. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/parse_cache.py +0 -0
  123. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/path_filters.py +0 -0
  124. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/perf.py +0 -0
  125. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/pr_impact.py +0 -0
  126. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/prepare_context.py +0 -0
  127. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/progress.py +0 -0
  128. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/ranking_engine.py +0 -0
  129. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/reconciliation.py +0 -0
  130. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/redactor.py +0 -0
  131. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/reference_facts.py +0 -0
  132. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/relevance_scorer.py +0 -0
  133. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/rename_refactor.py +0 -0
  134. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/repo_classifier.py +0 -0
  135. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/__init__.py +0 -0
  136. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/context.py +0 -0
  137. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/errors.py +0 -0
  138. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/executor.py +0 -0
  139. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/planner.py +0 -0
  140. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/query.py +0 -0
  141. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/request.py +0 -0
  142. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/resolution.py +0 -0
  143. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/result.py +0 -0
  144. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/retriever.py +0 -0
  145. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/runtime.py +0 -0
  146. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  147. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_graph.py +0 -0
  148. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_impact.py +0 -0
  149. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_intf.py +0 -0
  150. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_struct.py +0 -0
  151. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  152. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/runtime_classifier.py +0 -0
  153. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/scanner.py +0 -0
  154. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/schema.py +0 -0
  155. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  156. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/security_config.py +0 -0
  157. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/security_posture.py +0 -0
  158. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/semantic_analyzer.py +0 -0
  159. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/semantic_impact_engine.py +0 -0
  160. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/semantic_integration_engine.py +0 -0
  161. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/semantic_services.py +0 -0
  162. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_event_topology.py +0 -0
  163. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_findings.py +0 -0
  164. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_model.py +0 -0
  165. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_profiles.py +0 -0
  166. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_properties.py +0 -0
  167. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_security_audit.py +0 -0
  168. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_semantic.py +0 -0
  169. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
  170. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/summarizer.py +0 -0
  171. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/__init__.py +0 -0
  172. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/config.py +0 -0
  173. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/consent.py +0 -0
  174. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/events.py +0 -0
  175. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/filters.py +0 -0
  176. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/telemetry/transport.py +0 -0
  177. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/test_sources.py +0 -0
  178. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/token_estimate.py +0 -0
  179. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/tree_utils.py +0 -0
  180. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/type_usage_surface.py +0 -0
  181. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/validation_inference.py +0 -0
  182. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/validation_surface.py +0 -0
  183. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/verify_repo.py +0 -0
  184. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/verify_rules.py +0 -0
  185. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/version_check.py +0 -0
  186. {sourcecode-3.3.0 → sourcecode-3.5.0}/src/sourcecode/workspace.py +0 -0
  187. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/functions/README.md +0 -0
  188. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/functions/get-license/index.ts +0 -0
  189. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  190. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/functions/telemetry/index.ts +0 -0
  191. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/sql/license_event_ordering.sql +0 -0
  192. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/sql/licensing_schema.sql +0 -0
  193. {sourcecode-3.3.0 → sourcecode-3.5.0}/supabase/sql/telemetry_events.sql +0 -0
@@ -1,6 +1,358 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [3.5.0] — 2026-07-31
4
+
5
+ **M7.7 "Robustness floor" — the axis field evaluation #5 scored 4/10.** Ten pieces, and the
6
+ pattern in eight of them is the same: the reported symptom was real, and the measured cause was
7
+ not the one anyone had assumed. Input admission had four behaviours for one fact, not one broken
8
+ command. `posture`'s cost was not the Java parse but a quadratic scan of two branches that could
9
+ not fire — and that dead code was also a false zero on the security axis. The blast radius
10
+ disagreed with itself because there were two traversals of the reverse graph, not because reach
11
+ is hard. Two reported symptoms did not reproduce at all and are recorded as measured rather than
12
+ fixed blind.
13
+
14
+ **Minor, not patch**, on two counts: `impact` now publishes endpoints, transaction boundaries and
15
+ a security surface where it published `0`, so its risk score moves and a pipeline gating on it can
16
+ change colour; and `estimated_effort_days` is nullable where a range applies.
17
+
18
+ ### Changed
19
+
20
+ - **`retrieve` moves from `experimental` to `parked`** (C3-24) — kept working, no longer
21
+ developed, and saying so: `[PARKED]` in its own help, in the README table and in the user
22
+ guide, all rendered from `cli.COMMAND_TIERS`. The battery now also asserts that a parked
23
+ command declares itself, the twin of the assertion that already existed for experimental.
24
+ **Two of the three reported symptoms did not reproduce**, and are recorded that way rather
25
+ than repeated: `transaction-propagation` returns one entity because the battery repository
26
+ has exactly one non-default boundary (`REQUIRES_NEW: 1` of 161) — `observations.total` is the
27
+ tx-index population standing beside the answer, not a truncated total, and a `--limit` exists
28
+ (default unbounded) that records `entities_truncated` when it bites; `security-surface`
29
+ returns 0 where `spring-audit --scope security` also returns 0. What does reproduce is an
30
+ `explanation` block shaped for a ranking that never ran. The surface is not non-functional —
31
+ it is undifferentiated, re-stating behind 15+ intents what the core commands answer better.
32
+
33
+ ### Measured
34
+
35
+ - **`review-pr`'s path argument is not decorative — the reported scope defect does not
36
+ reproduce** (C3-27, scope half). Field evaluation #5 reported that given a submodule path the
37
+ command reports the *enclosing* repository as `repo_root` and reviews 227 files from other
38
+ modules. Re-measured on six shapes: a module subdirectory, that same subdirectory as the
39
+ working directory, a nested path given relative to the root, an embedded repository, a **real
40
+ git submodule** (where `.git` is a gitlink *file*, so an `exists()` test is true for it and the
41
+ walk up has to stop there), and a submodule asked for a ref only its parent has. Every run is
42
+ bounded to the path it was given: on BroadleafCommerce the scoped runs return exactly
43
+ `git diff --name-only <ref> -- <scope>` — 5 of 17 files at `core/`, 855 at
44
+ `core/broadleaf-framework/`, 1 222 at `core/` over a 3 332-file range — `_meta.scope` names the
45
+ tree answered about, no sibling-module path appears anywhere in the payload, and the
46
+ missing-ref case is refused (`git_ref_not_found`, exit 1) instead of borrowed from the parent.
47
+ Recorded as not reproducing rather than fixed blind, and the six shapes are now asserted in
48
+ `tests/test_git_scope_namespace.py` so the symptom cannot return silently. The probable closer
49
+ is the Bug A scope-namespace seam, which landed between the evaluated build and this
50
+ measurement.
51
+
52
+ ### Fixed
53
+
54
+ - **`impact` reported `endpoints_affected_count: 0` for a symbol `impact-chain` reached 21
55
+ endpoints from** (C1-16, endpoint half — the caller half closed in 3.5.0). A false zero on the
56
+ blast-radius axis, which is the number a user takes a risk decision with. The cause was
57
+ measured, not guessed: there were **two traversals** of the reverse graph, and they disagreed
58
+ twice.
59
+ - `impact` looked up the exact reverse-graph key. The callers of a class are stored under its
60
+ **method** keys (`Foo#doWork`), never under `Foo`, so the walk terminated at every
61
+ class-level node — which is exactly what a DI `injects` edge normalises to. 125 indirect
62
+ callers instead of 1 442.
63
+ - It admitted `implements`/`extends` as caller edges (an implementor does not call an
64
+ interface by implementing it, CH-006) under a comment claiming its edge set was *"consistent
65
+ with `spring_impact._SKIP_EDGE_TYPES`"*, which skips both. And it held no interface index, so
66
+ it never crossed the DI boundary at all: on `Money` it reached 200 classes and **zero
67
+ controllers**, against 441 and 18.
68
+ - New authority `sourcecode/caller_reach.py`, registered as fact `blast_radius_reach`, with
69
+ `impact`, `impact-chain` and `migrate-check --blast-radius` all bound to it. On
70
+ BroadleafCommerce's `Money`, `impact` now reports **20 of the 21 endpoints** (0 before),
71
+ **53 transactional boundaries** (0), **20 persistence paths** (2) and **15 security-gated
72
+ endpoints** (0) — while `stats.direct_caller_count` stays **134**, so the caller axis closed
73
+ in 3.5.0 does not move. Parity verified command-to-command on spring-petclinic
74
+ (`OwnerRepository`: 13 = 13 endpoints, exact) and open-banking-gateway (two symbols, exact):
75
+ the reconciliation adds no false positives. Cost unchanged (5.0 s vs 4.9 s on that repo).
76
+ - Also fixed here: the explanation prose read *"433 direct callers"* beside a payload
77
+ publishing 134 caller classes and 433 reference sites, each named for its unit. Prose is
78
+ what a reader quotes, so it now names its unit too.
79
+ - **Residual, stated because it is measured:** one endpoint of the 21
80
+ (`BroadleafOauthRegisterController#processRegister`) is still reached by `impact-chain` and
81
+ not by `impact`, because the two commands read *different graphs* — `impact` builds the repo
82
+ IR, `impact-chain` the CIR, and the CIR carries a method key for that handler the repo IR
83
+ does not. That is a graph-population gap, not a traversal one. Seed expansion is asymmetric
84
+ for the same reason (`impact-chain` expands a seed to its subtypes, `impact` only to the
85
+ interfaces it implements).
86
+
87
+ - **`posture` costs a sixth of what it did, and a warm is finally worth something to it**
88
+ (C3-25) — 24.5 s → **10.1 s** cold and 19.3 s → **1.6 s** after `cache warm`, measured on
89
+ BroadleafCommerce in isolation. Field evaluation #5 measured 195 s, ×18 the session median,
90
+ on the one command the product differentiates on, and the cache model honestly documented
91
+ that a warm bought it almost nothing. Both had the same cause, and it was not the Java parse
92
+ everyone blamed:
93
+ - **The security predicate re-walked every node in the repository for every class** —
94
+ 6 075 × ~29 000 ≈ 176 M dictionary reads on the battery repository, larger than the parse
95
+ itself — looking for `node["return_type"]` and `node["supertypes"]`. **The IR publishes
96
+ neither key on a node.** Inheritance and return types are `extends`, `implements` and
97
+ `returns` *edges*, so both branches were structurally dead in every release: a repository
98
+ that wires its chain with a `@Bean SecurityFilterChain` or a filter subclass and no
99
+ `@EnableWebSecurity` was reported as configuring no security at all — a **false zero on the
100
+ axis this command exists for**. The predicate now reads the edges, once per IR. On
101
+ BroadleafCommerce it recovers 6 request-chain configurations, verified 6/6 against source
102
+ with no false positive, one of which is `@ConditionalOnProperty` and now correctly qualifies
103
+ the endpoint claim as unresolved instead of silently strengthening it.
104
+ - **`posture` built its own CIR on every run** — the same parse `cache warm`, `explain` and
105
+ `impact-chain` already share. It now goes through the shared knowledge cache, poison-safe by
106
+ the `peek_cir` rule: the knowledge key is repo-wide and carries no scope, so a run bounded to
107
+ a subdirectory neither reads nor writes it and keeps its own unshared build.
108
+ - **`--diff` parsed the repository once per side.** The IR does not depend on which profiles
109
+ are asked about — only the resolution over it does — so the second side now costs the
110
+ resolution alone: 2.3 s warm for a two-set comparison, the shape a per-PR gate runs.
111
+
112
+ - **A degraded run now states the effect of the degradation** — invariant **I-8**, accepted and
113
+ enforced (C3-27, and the class field evaluation #5 named). `review-pr` published
114
+ `analysis_limiter: {"missing_signals": ["dependency_graph", "import_graph"]}`: what was
115
+ absent, and nothing about what its absence changed. A reader could not tell whether the answer
116
+ was a floor, a ceiling or unaffected — the same defect shape as `statically_unreferenced: 0`,
117
+ one level up.
118
+ - Every signal a command can report as missing now carries **what it feeds**, **what the
119
+ answer does without it**, and a **`direction`**: `under_reports` (read the answer as a
120
+ floor — absence of a finding is not evidence of none), `over_reports` (a ceiling), or
121
+ `undetermined`. Mixed directions collapse to `undetermined`: an answer that is a floor in
122
+ one place and a ceiling in another is neither. The block ends with one sentence on how to
123
+ read the result, and the GitHub comment prints it beside the `OMITTED` badge — a reviewer
124
+ reads the comment, not the JSON.
125
+ - One authority (`sourcecode/degradation.py`) serves every emitter, and the battery discovers
126
+ signal names **from the source by AST**, so a signal added without an effect statement fails
127
+ the suite. That scan found a second emitter the field never reached: **`archetype`** declares
128
+ `repo_ir_subsystem_masses` and `semantic_ir_spi_fanin` missing on *every* run — signals wired
129
+ into no release — and its missing `endpoints` signal enters the score as `0.0` rather than as
130
+ unknown, so an HTTP-facing repository is scored as if it exposed no HTTP surface. Both now
131
+ say so in the payload; correcting the score itself is separate work.
132
+ - **The second family is now covered too: what the product cuts on purpose.** A missing signal
133
+ is something the run did not have; a **cap** is something it had and chose not to publish.
134
+ Those said only *that* they capped — `"_truncation_summary": {"total_omitted_items": 1442}`,
135
+ `"bfs_truncation_reason": "hub_class_depth_cap"`, `"is_stale": true` — and a count of omitted
136
+ items is not an effect. `CAP_EFFECTS` gives each one the same three fields plus a fourth,
137
+ `exact`, which is what separates a cap from a missing signal: what survived the cut.
138
+ - **The output budget** publishes the least intuitive effect of all: *nothing moved*. Every
139
+ figure is computed before serialisation, so the counts stay exact and only the lists became
140
+ samples — and deriving a count from a truncated list is exactly how the field arrived at
141
+ four contradictory fan-in figures. The block now says so, and the battery asserts a trimmed
142
+ payload keeps its count.
143
+ - **Display caps** (`impact-chain --limit`, `impact`'s 30-class sample, `retrieve --limit`)
144
+ state that the total beside the list was computed over the whole population, so list and
145
+ total disagreeing is construction, not contradiction.
146
+ - **The hub-class depth cap** names every figure it turns into a floor — indirect callers and
147
+ the endpoints, transaction boundaries, security surface and risk score derived from them —
148
+ and every figure it leaves exact. A zero under a cap means the capped walk found none.
149
+ - **A stale snapshot** is `undetermined`, not a floor: it describes a different tree, so a
150
+ symbol added since is missing *and* a symbol deleted since is still listed.
151
+ - **An unavailable section** (`migrate-check --blast-radius` when the IR cannot be built)
152
+ says the failure is bounded to that block and the findings beside it are untouched.
153
+ - Same enforcement as the signal family: the battery discovers cap names from the source by
154
+ AST, so a cap applied without an effect statement fails the suite.
155
+
156
+ - **A run that succeeded now writes nothing to stderr** (C3-26). PowerShell 5.1 — a declared
157
+ target platform — turns any stderr write from a native command into a `NativeCommandError`,
158
+ so an informational line makes a successful run look like a failed one and forces the caller
159
+ to swallow errors it should be reporting. Measured non-TTY before fixing: `migrate-check`
160
+ wrote a size advisory, `validation` a note about an empty result, `explain` a cache hit/miss
161
+ line; everything else was already clean. Those three now go through one seam (`cli._notice`)
162
+ that speaks only when stderr is a terminal. No `--quiet` flag: a flag has to be remembered by
163
+ every caller, on the platform where forgetting it is fatal.
164
+ - **`cache warm` and `cache clear` print their status on stdout**, where `cache status` in the
165
+ same group already printed it. Their status *is* their answer — on stderr, `ask cache warm >
166
+ log.txt` captured nothing at all.
167
+ - Errors keep stderr, where they pair with a non-zero exit; `tests/test_stderr_purity_matrix.py`
168
+ asserts both halves across 13 commands through a real subprocess, because a CliRunner blurs
169
+ the two streams — which is how three of these survived the stdout-purity battery that
170
+ already existed.
171
+
172
+ - **Every example the CLI prints now parses against the CLI that prints it** (C3-16). The root
173
+ help led its gating panel with `pr-impact . --since main`; `pr-impact` has no `--since`. It
174
+ was reported by three field evaluations and survived all three, because the check was a
175
+ person re-reading a hand-written block — and the header panels print invocations *without*
176
+ the `ask ` prefix, so even grepping for `ask ` missed it. The example is now
177
+ `pr-impact . --files -` (the stdin form, verified against a real `git diff --name-only`), and
178
+ `tests/test_help_examples.py` extracts all 156 invocations from every help screen — root
179
+ panels, command docstrings, group and subcommand help — and parses each one against the
180
+ command it names. The battery states its own scope: the command and its options exist and
181
+ take the arity given; values are not validated and nothing is executed.
182
+
183
+ - **One class, four fan-in figures — two of them wrong about their own unit** (C1-16, field
184
+ evaluation #5). On `org.broadleafcommerce.common.money.Money` the product published 1 868,
185
+ 673, 418 and 151 under names that all read as fan-in, and the note written to pre-empt that
186
+ confusion promised an ordering (`modernize.in_degree` *"always the largest"*, the other two
187
+ within *"a small margin"*) that measurement falsified by 2,8× and 177 %. This is the number
188
+ a risk decision is taken with: *"¿el blast radius son 484 o 611 clases?"*
189
+ - **`impact.stats.direct_caller_count` counted caller *symbols*** — 418, of which 346 were
190
+ method-level, so a class was counted once per method touching the target. It now counts
191
+ **distinct classes** (134) with the reference sites beside it as
192
+ **`direct_caller_symbol_count`** (418). The risk score still weighs reference sites; it was
193
+ the published unit that was false. `direct_callers` is a class list (`direct_caller_symbols`
194
+ keeps the sites), so the array and its count are finally the same unit.
195
+ - **`explain.incoming_callers` deduplicated by simple name**, collapsing 186 distinct classes
196
+ into 151 and dropping 35 real dependents. Identity is the FQN; a colliding simple name is
197
+ rendered qualified. New **`incoming_callers_count`** is measured before the `--limit` cap,
198
+ so a consumer never has to take `len()` of a truncated array.
199
+ - **`impact-chain.metadata.callers_total` was the size of the whole chain**, not fan-in, and
200
+ was absent from the note. It stays (removing a published key is breaking) and now says so,
201
+ beside **`chain_classes_total`** (441) and **`direct_caller_count`** (134) — which equals
202
+ `impact`'s figure by construction. `explain` is a declared superset: it admits import-only
203
+ references, a blast radius does not.
204
+ - **The note is generated from a registry** (`caller_metrics.FAN_IN_FIGURES`), each figure
205
+ carrying its unit and the references it admits, and the battery asserts every figure appears
206
+ with its unit. No ordering between units is promised any more — the three falsified phrases
207
+ are banned by test. Registered as fact `caller_fan_in` (ADR-0008 R11-R14).
208
+ - `compare`'s evidence trail read `len()` of the 30-item display array and told every hub
209
+ class in the repository it had "30 direct callers". It reads `stats` now.
210
+ - Still open and stated rather than guessed: `impact` reports `endpoints_affected_count: 0`
211
+ where `impact-chain` returns 21 endpoints for the same class. Measured today, the two
212
+ traversals reach different populations (259 vs 441 classes at full depth), so that half is
213
+ engine reach, not presentation.
214
+
215
+ - **A command must not answer a question about a repository it could not read** (C3-22, C3-23,
216
+ the robustness finding of field evaluation #5 — *"un gate que puede pasar sin analizar nada
217
+ es peor que no tener gate, porque genera confianza injustificada"*). The same nonexistent
218
+ path produced four different behaviours: six commands refused it correctly, `verify`
219
+ answered `{"verdict": "pass", "exit_code": 0}`, `posture` answered an all-zeros payload with
220
+ exit 0, `archetype` raised a `FileNotFoundError` traceback through the JSON-always contract,
221
+ and `cold-start` reported `no_ris`. In `ask verify $DIR --fail-on new`, one typo in the
222
+ variable was a permanently green pipeline.
223
+ - **One admission authority** (`sourcecode.path_admission`): "can this path be analysed?" is
224
+ one fact, so **25 path-taking commands** now derive it in one place instead of twenty
225
+ inline copies plus four omissions. The refusal carries a machine-readable **`reason`** —
226
+ `missing`, `not_a_directory`, or `unreadable`.
227
+ - **`unreadable` is new and was not in the field report.** A directory that exists but cannot
228
+ be listed produced the same `archetype` traceback; it is now refused before the scan starts.
229
+ - **`verify` and `verify-edit` refuse with exit 2, not 1.** Their published contract reserves
230
+ 2 for *unverified* — the run that could not look — as distinct from 1, the run that looked
231
+ and found violations. A gate that never analysed anything reports neither `pass` nor a
232
+ violation count.
233
+ - **`impact-chain` and `impact` name the repository they actually read.** A path in the
234
+ symbol slot made them report `not_found` about the current working directory. A file path
235
+ is still a legitimate symbol; a symbol that *is* a directory, or that carries a path
236
+ separator and does not exist, now says which repository was analysed and how the arguments
237
+ are ordered.
238
+ - The battery discovers path-taking commands from the CLI itself: a new command that skips
239
+ the gate fails the suite.
240
+
241
+ - **An effort figure is a point only when nothing under it is a range** (C1-12, the third
242
+ reconciliation gate of field evaluation #4). `migrate-check` folded the Hibernate 5→6 slice
243
+ into the headline as the **midpoint** of a range the same report labels `confidence: low`,
244
+ and that the Hibernate effort model itself calls a non-deduplicated *upper bound*. Measured
245
+ on the battery: BroadleafCommerce billed **157.7 d, 153.7 of them (97%) from 76.5–230.8**;
246
+ openmrs-core 76.4 d with 62.2 (81%) from 28.9–95.6. The arithmetic was right and the claim
247
+ was still false — a 3× span presented as one number.
248
+ - New top-level **`estimated_effort_range_days`** `{low, high, confidence, basis}`. The band
249
+ is the worst confidence of the contributing slices; findings effort is a per-file
250
+ measurement and enters as a point.
251
+ - **`estimated_effort_days` is `null`** when a range slice is in the total (never `0` — that
252
+ reads as "no work"), and stays a number on every repo where no range applies. The text
253
+ report prints the span: `Estimated effort: 80.6–234.9d (low confidence — not a point
254
+ estimate)`.
255
+ - `effort_breakdown.hibernate_rewrite_effort_days` is `null`: the midpoint is not a
256
+ measurement of anything, so it is no longer published. The range sits beside it in
257
+ `hibernate_rewrite_range`, with `hibernate_rewrite_effort_basis` and `unestimated_slices`.
258
+ - **An inferred version buys no number.** When the 5→6 axis applies only because a Spring
259
+ Boot 2 BOM manages Hibernate 5.x — no version declared in the build — the rewrite *scope*
260
+ is a hypothesis: the slice contributes neither a point nor a range, and the gap is named.
261
+ The readiness dimension stays applicable; not estimating is not pretending it is absent.
262
+ - Readiness snapshots/trends carry `estimated_effort_low_days` / `estimated_effort_high_days`
263
+ so the series does not go blind exactly where the effort is largest.
264
+
265
+ ## [3.4.0] — 2026-07-29
266
+
267
+ **Two of the five reconciliation gates, and one commercial surface that answered five ways.**
268
+ The theme of field evaluation #4 was not missing analysis: it was analysis the product had
269
+ already done and did not consult — a migration estimate that ignored its own
270
+ `auto_fix_available`, and a gate that reported `pass` for a file it never looked at.
271
+
272
+ **A minor, not a patch, and for one reason: `verify-edit` can now exit 2 where it exited 0.**
273
+ No API changed, but a build-configuration-only edit stops being a green light, so an installed
274
+ hook or a pipeline that gates on the exit code will start blocking on it. A patch release must
275
+ never be able to turn a green pipeline red.
276
+
277
+ **Upgrade notes.**
278
+
279
+ - `verify-edit`: a change to `pom.xml`, `build.gradle`, `application*.yml`, `web.xml` or a
280
+ container descriptor now yields `unverified` (exit 2). `git commit --no-verify` and
281
+ `ask verify-edit --no-ci` are the escape hatches.
282
+ - `migrate-check`: published effort numbers **move down** — the same work, split by what it
283
+ actually is. Measured: openmrs-core 122.1 → 76.4 d, BroadleafCommerce 163.3 → 157.7,
284
+ alfresco 22.8 → 5.2. `blocking_count` and `readiness_score` are unchanged.
285
+ - `auth status`: new authoritative fields; `pro`, `pro_effective`, `pro_gating` and `pro_reason`
286
+ still ship, now derived from the authority and listed in `deprecated_fields`.
287
+
288
+ ### Fixed
289
+
290
+ - **`verify-edit` sees the files that decide what the code compiles to** (C3-14, the second
291
+ reconciliation gate). Editing a `pom.xml` produced `changed_files: []`, `verdict: pass`,
292
+ `exit_code: 0` — and `blast_radius: {"confidence": "high", "note": "no changed files"}`.
293
+ git reported the file; the gate did not look at it, then said everything was fine about a
294
+ change that moves every source file in the repository at once.
295
+ - New blocking axis **`build_config_changed`**, and a new `changed_build_files` list beside
296
+ `changed_files`. It names what moved: `effective_compiler_release` (from → to) and
297
+ `dependency_delta` per coordinate.
298
+ - **The verdict is `unverified`, not `break`.** A build edit is neither a proven regression
299
+ nor computed-clean, which is exactly what `changed: null` has always meant here — so it
300
+ flows through the existing "could not compute" path and exits 2. No special case.
301
+ - Admission is by file name and deliberately narrow — `pom.xml`, `build.gradle`,
302
+ `settings.gradle`, `gradle.properties`, `application*.yml|properties`, `bootstrap*`,
303
+ `web.xml`, `weblogic*.xml`, `jboss-*.xml`, `*-context.xml`, `persistence.xml`, `beans.xml`.
304
+ `messages.properties` and `logback.xml` are out: every admitted file degrades a verdict, and
305
+ a loose rule turns the gate into noise people learn to ignore.
306
+ - `blast_radius` stops reporting `confidence: "high"` over a change whose reach it has not
307
+ measured.
308
+ - **Upgrade note (behaviour change):** a build-configuration-only edit now exits **2**
309
+ (unverified) where it exited 0 (pass). An installed pre-commit/pre-push hook will block on
310
+ such an edit; `git commit --no-verify` remains the escape hatch, and `--no-ci` still reports
311
+ without gating.
312
+
313
+ ### Changed
314
+
315
+ - **The migration estimate reads the report's own `auto_fix_available`** (C1-11, the first of
316
+ the five reconciliation gates from field evaluation #4). `migrate-check` shipped an
317
+ OpenRewrite recipe for most findings and then billed every one of them at the hand-edit rate.
318
+ Reproduced on the battery before fixing: **openmrs-core carries a recipe for 296 of 310
319
+ findings and costed all 310 as hand work**, for a headline of 122.1 person-days.
320
+ - `effort_breakdown` splits the same total into **`manual_days`** + **`mechanical_days`** +
321
+ `hibernate_rewrite_effort_days`. The partition is **by file**, because the severity weights
322
+ are per file: a file is mechanical only when *every* finding in it has a recipe, and one
323
+ unautomatable finding costs the whole file manually — a human is in that file either way.
324
+ - **The review factor is published, not buried.** `mechanical_review_factor` (0.2) carries
325
+ `mechanical_review_factor_basis: "assumption, not a measurement"`, so a team can substitute
326
+ their own number instead of inheriting ours silently. `recipes[]` lists what would run and
327
+ `mechanical_basis` states both denominators.
328
+ - **`validation_days` is `null` with a note, never `0`** — the re-test scope is a different
329
+ measurement (`migrate-check --blast-radius`), and `0` would read as "no re-testing needed"
330
+ (invariant I-3).
331
+ - **Published numbers move.** Measured: openmrs-core 122.1 → **76.4 d**, BroadleafCommerce
332
+ 163.3 → **157.7 d**, alfresco 22.8 → **5.2 d**. `blocking_count` and `readiness_score` are
333
+ unchanged in all three — the split changes what the work is called, never what blocks.
334
+
335
+ - **`ask auth status` answers one question instead of five** (P-2). It used to publish
336
+ `{"status": "unauthenticated", "pro": true, "pro_reason": "early-adoption unlock"}` — every
337
+ field true on its own axis, and together unreadable: a buyer could not tell what they have,
338
+ why they have it, or what changes when it ends.
339
+ - One authority, `license.entitlement()`, now answers it: **`entitlement`** (`pro`/`free` —
340
+ what runs today), **`source`** (why: `license_key`, `early_adoption_unlock`, `free_tier`),
341
+ **`authenticated`** (whether a credential exists here — a *separate* fact, since today one
342
+ can be entitled without one), **`paywall_active`**, **`when_it_changes`** (what you lose
343
+ when that source stops applying) and **`free_repo_java_file_limit`** (the number the gate
344
+ actually compares against).
345
+ - `when_it_changes` states the gate in the terms the gate uses and **never quotes a price** —
346
+ pricing lives in `docs/PRODUCT_TIERS.md`, and a number printed here would be stale the day
347
+ it moves. Asserted.
348
+ - **`is_pro` is derived from the same authority.** It is what every gate reads
349
+ (`can_use`, `require_feature`), and it used to be computed beside the status block rather
350
+ than from it — which is how a status page came to contradict the commands next to it.
351
+ - `pro`, `pro_effective`, `pro_gating` and `pro_reason` are still emitted for existing
352
+ consumers, are derived from the authority (so they cannot drift), and are listed in the new
353
+ `deprecated_fields`. They are removed in a future major.
354
+ - `plan_status` no longer reports `unknown` for a licence file with no explicit status while
355
+ the authority reads that same file as active.
4
356
 
5
357
  ## [3.3.0] — 2026-07-29
6
358
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 3.3.0
3
+ Version: 3.5.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -220,8 +220,8 @@ experimental: both are true, and they are two different facts.
220
220
  |---|---|---|
221
221
  | **core** | contract stable within a major — safe to gate CI on | `endpoints` · `spring-audit` · `migrate-check` · `impact` · `impact-chain` · `pr-impact` · `verify` |
222
222
  | **supported** | maintained; fields are added, never removed without a major | every command not named in another row |
223
- | **experimental** | shape may change in a minor — do not gate CI on it | `posture` · `archetype` · `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 |
@@ -283,10 +283,13 @@ Honest limits worth knowing before you rely on it:
283
283
  > full Pro entitlements — no size gate, no key. The tiers below describe the model the
284
284
  > paywall will return to later.
285
285
  >
286
- > **What that means concretely.** `ask auth status` reports `"status": "unauthenticated"`
287
- > together with `"pro": true`, `"pro_reason": "early-adoption unlock"` that combination is
288
- > expected, not a bug: you are unauthenticated *and* unlocked. When the unlock ends, gating
289
- > returns **by repo size and automation, never by command**: `posture`, `endpoints`,
286
+ > **What that means concretely.** Ask the product: `ask auth status` answers in one block —
287
+ > `entitlement` (what runs today), `source` (*why* — a licence, the unlock, or the free tier),
288
+ > `authenticated` (whether a credential exists, which is a separate fact) and
289
+ > `when_it_changes` (what you lose when that source stops applying). A fresh install reads
290
+ > `entitlement: pro`, `source: early_adoption_unlock`, `authenticated: false` — unauthenticated
291
+ > *and* entitled, stated as two facts instead of one contradiction. When the unlock ends,
292
+ > gating returns **by repo size and automation, never by command**: `posture`, `endpoints`,
290
293
  > `spring-audit` and `migrate-check` stay in the base tier at full output. Nothing you can run
291
294
  > today becomes a paid-only command tomorrow.
292
295
 
@@ -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 |
@@ -245,10 +245,13 @@ Honest limits worth knowing before you rely on it:
245
245
  > full Pro entitlements — no size gate, no key. The tiers below describe the model the
246
246
  > paywall will return to later.
247
247
  >
248
- > **What that means concretely.** `ask auth status` reports `"status": "unauthenticated"`
249
- > together with `"pro": true`, `"pro_reason": "early-adoption unlock"` that combination is
250
- > expected, not a bug: you are unauthenticated *and* unlocked. When the unlock ends, gating
251
- > returns **by repo size and automation, never by command**: `posture`, `endpoints`,
248
+ > **What that means concretely.** Ask the product: `ask auth status` answers in one block —
249
+ > `entitlement` (what runs today), `source` (*why* — a licence, the unlock, or the free tier),
250
+ > `authenticated` (whether a credential exists, which is a separate fact) and
251
+ > `when_it_changes` (what you lose when that source stops applying). A fresh install reads
252
+ > `entitlement: pro`, `source: early_adoption_unlock`, `authenticated: false` — unauthenticated
253
+ > *and* entitled, stated as two facts instead of one contradiction. When the unlock ends,
254
+ > gating returns **by repo size and automation, never by command**: `posture`, `endpoints`,
252
255
  > `spring-audit` and `migrate-check` stay in the base tier at full output. Nothing you can run
253
256
  > today becomes a paid-only command tomorrow.
254
257
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "3.3.0"
7
+ version = "3.5.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.3.0"
7
+ __version__ = "3.5.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.",