sourcecode 5.3.0__tar.gz → 5.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. {sourcecode-5.3.0 → sourcecode-5.3.1}/CHANGELOG.md +52 -0
  2. {sourcecode-5.3.0 → sourcecode-5.3.1}/PKG-INFO +3 -3
  3. {sourcecode-5.3.0 → sourcecode-5.3.1}/README.md +2 -2
  4. {sourcecode-5.3.0 → sourcecode-5.3.1}/pyproject.toml +1 -1
  5. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/audit_report.py +19 -2
  7. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/cli.py +4 -1
  8. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/consumer_join.py +36 -1
  9. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/repository_ir.py +66 -39
  10. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/ris.py +54 -8
  11. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/runs.py +21 -2
  12. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/summarizer.py +8 -2
  13. {sourcecode-5.3.0 → sourcecode-5.3.1}/.github/workflows/build-windows.yml +0 -0
  14. {sourcecode-5.3.0 → sourcecode-5.3.1}/.github/workflows/perf-gate.yml +0 -0
  15. {sourcecode-5.3.0 → sourcecode-5.3.1}/.gitignore +0 -0
  16. {sourcecode-5.3.0 → sourcecode-5.3.1}/.ruff.toml +0 -0
  17. {sourcecode-5.3.0 → sourcecode-5.3.1}/CLAUDE.md +0 -0
  18. {sourcecode-5.3.0 → sourcecode-5.3.1}/CONTRIBUTING.md +0 -0
  19. {sourcecode-5.3.0 → sourcecode-5.3.1}/LICENSE +0 -0
  20. {sourcecode-5.3.0 → sourcecode-5.3.1}/SECURITY.md +0 -0
  21. {sourcecode-5.3.0 → sourcecode-5.3.1}/raw +0 -0
  22. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/compare_integration_engines.py +0 -0
  23. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/customer_smoke_test.sh +0 -0
  24. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/generate_jdk_exports.py +0 -0
  25. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/perf_gate.py +0 -0
  26. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/perf_harness.py +0 -0
  27. {sourcecode-5.3.0 → sourcecode-5.3.1}/scripts/sync_gate_anchors.py +0 -0
  28. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/adaptive_scanner.py +0 -0
  29. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/archetype.py +0 -0
  30. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/architectural_baseline.py +0 -0
  31. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/architectural_delta.py +0 -0
  32. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/architecture_analyzer.py +0 -0
  33. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/architecture_summary.py +0 -0
  34. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/ast_extractor.py +0 -0
  35. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/baseline_autocapture.py +0 -0
  36. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/cache.py +0 -0
  37. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/cache_model.py +0 -0
  38. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/call_surface.py +0 -0
  39. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/caller_metrics.py +0 -0
  40. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/caller_reach.py +0 -0
  41. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/canonical_ir.py +0 -0
  42. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/chain_rules.py +0 -0
  43. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/change_plan.py +0 -0
  44. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/cir_graphs.py +0 -0
  45. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/classifier.py +0 -0
  46. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/client_calls.py +0 -0
  47. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/code_notes_analyzer.py +0 -0
  48. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/compare.py +0 -0
  49. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/confidence_analyzer.py +0 -0
  50. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/constraint_diff.py +0 -0
  51. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/container_wiring.py +0 -0
  52. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/context_cache.py +0 -0
  53. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/context_graph.py +0 -0
  54. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/context_scorer.py +0 -0
  55. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/context_summarizer.py +0 -0
  56. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/contract_diff.py +0 -0
  57. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/contract_init.py +0 -0
  58. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/contract_model.py +0 -0
  59. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/contract_pipeline.py +0 -0
  60. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/coverage_parser.py +0 -0
  61. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/data_exposure.py +0 -0
  62. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/data_labels.py +0 -0
  63. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/declarations.py +0 -0
  64. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/defect_identity.py +0 -0
  65. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/degradation.py +0 -0
  66. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/dependency_analyzer.py +0 -0
  67. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/deployment_prefix.py +0 -0
  68. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detach.py +0 -0
  69. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/__init__.py +0 -0
  70. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/base.py +0 -0
  71. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
  72. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/dart.py +0 -0
  73. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/dotnet.py +0 -0
  74. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/elixir.py +0 -0
  75. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/go.py +0 -0
  76. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/heuristic.py +0 -0
  77. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/hybrid.py +0 -0
  78. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/java.py +0 -0
  79. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
  80. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/nodejs.py +0 -0
  81. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/parsers.py +0 -0
  82. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/php.py +0 -0
  83. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/project.py +0 -0
  84. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/python.py +0 -0
  85. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/ruby.py +0 -0
  86. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/rust.py +0 -0
  87. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/systems.py +0 -0
  88. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/terraform.py +0 -0
  89. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/detectors/tooling.py +0 -0
  90. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/doc_analyzer.py +0 -0
  91. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
  92. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/endpoint_literals.py +0 -0
  93. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/endpoint_metrics.py +0 -0
  94. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/entrypoint_classifier.py +0 -0
  95. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/env_analyzer.py +0 -0
  96. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/envelope.py +0 -0
  97. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/environment_resolution.py +0 -0
  98. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/error_schema.py +0 -0
  99. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/evidence_provider.py +0 -0
  100. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/execution_plan.py +0 -0
  101. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/explain.py +0 -0
  102. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/facts/__init__.py +0 -0
  103. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/facts/registry.json +0 -0
  104. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/file_chunker.py +0 -0
  105. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/file_classifier.py +0 -0
  106. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/filter_surface.py +0 -0
  107. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/format_contract.py +0 -0
  108. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/fqn_utils.py +0 -0
  109. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/gate_anchors.py +0 -0
  110. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/git_analyzer.py +0 -0
  111. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/git_checkout.py +0 -0
  112. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/graph_analyzer.py +0 -0
  113. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/graph_evidence.py +0 -0
  114. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/hibernate_strat.py +0 -0
  115. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/identity_fallback.py +0 -0
  116. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/integration_coordinates.py +0 -0
  117. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/jdk_exports.py +0 -0
  118. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/license.py +0 -0
  119. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/__init__.py +0 -0
  120. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/dependency_import.py +0 -0
  121. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  122. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  123. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  124. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  125. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  126. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/orchestrator.py +0 -0
  127. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/registry.py +0 -0
  128. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/runner.py +0 -0
  129. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp/server.py +0 -0
  130. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/mcp_nudge.py +0 -0
  131. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/metrics_analyzer.py +0 -0
  132. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/migrate_check.py +0 -0
  133. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/migration_blast.py +0 -0
  134. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/non_coverage.py +0 -0
  135. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/openapi_surface.py +0 -0
  136. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/openrewrite_recipe.py +0 -0
  137. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/output_budget.py +0 -0
  138. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/output_encoding.py +0 -0
  139. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/parallel.py +0 -0
  140. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/parse_cache.py +0 -0
  141. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/partial_contract.py +0 -0
  142. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/path_admission.py +0 -0
  143. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/path_filters.py +0 -0
  144. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/perf.py +0 -0
  145. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/phased_run.py +0 -0
  146. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/pipe_contract.py +0 -0
  147. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/posture.py +0 -0
  148. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/pr_comment_renderer.py +0 -0
  149. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/pr_impact.py +0 -0
  150. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/prepare_context.py +0 -0
  151. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/progress.py +0 -0
  152. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/provenance.py +0 -0
  153. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/ranking_engine.py +0 -0
  154. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/readiness_timeline.py +0 -0
  155. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/readonly.py +0 -0
  156. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/reconciliation.py +0 -0
  157. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/redactor.py +0 -0
  158. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/reference_facts.py +0 -0
  159. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/release_info.py +0 -0
  160. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/relevance_scorer.py +0 -0
  161. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/remedies.py +0 -0
  162. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/rename_refactor.py +0 -0
  163. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/repo_classifier.py +0 -0
  164. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/__init__.py +0 -0
  165. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/context.py +0 -0
  166. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/errors.py +0 -0
  167. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/executor.py +0 -0
  168. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/planner.py +0 -0
  169. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/query.py +0 -0
  170. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/request.py +0 -0
  171. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/resolution.py +0 -0
  172. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/result.py +0 -0
  173. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/retriever.py +0 -0
  174. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/runtime.py +0 -0
  175. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps.py +0 -0
  176. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  177. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
  178. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
  179. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
  180. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
  181. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  182. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/risk.py +0 -0
  183. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/rule_catalog.py +0 -0
  184. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/rule_pass.py +0 -0
  185. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/runtime_classifier.py +0 -0
  186. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/sarif.py +0 -0
  187. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/scanner.py +0 -0
  188. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/schema.py +0 -0
  189. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  190. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/security_chain.py +0 -0
  191. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/security_config.py +0 -0
  192. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/security_config_scan.py +0 -0
  193. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/security_posture.py +0 -0
  194. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/semantic_analyzer.py +0 -0
  195. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/semantic_impact_engine.py +0 -0
  196. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/semantic_integration_engine.py +0 -0
  197. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/semantic_services.py +0 -0
  198. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/serializer.py +0 -0
  199. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/servlet_surface.py +0 -0
  200. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/source_text.py +0 -0
  201. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_event_topology.py +0 -0
  202. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_findings.py +0 -0
  203. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_impact.py +0 -0
  204. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_model.py +0 -0
  205. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_profiles.py +0 -0
  206. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_properties.py +0 -0
  207. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_security_audit.py +0 -0
  208. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_semantic.py +0 -0
  209. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
  210. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/sql_taint.py +0 -0
  211. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/target_admission.py +0 -0
  212. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/__init__.py +0 -0
  213. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/config.py +0 -0
  214. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/consent.py +0 -0
  215. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/events.py +0 -0
  216. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/filters.py +0 -0
  217. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/telemetry/transport.py +0 -0
  218. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/test_gap_ranking.py +0 -0
  219. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/test_sources.py +0 -0
  220. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/text_input.py +0 -0
  221. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/timeline.py +0 -0
  222. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/token_estimate.py +0 -0
  223. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/tree_utils.py +0 -0
  224. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/type_usage_surface.py +0 -0
  225. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/validation_inference.py +0 -0
  226. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/validation_surface.py +0 -0
  227. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/verify_edit.py +0 -0
  228. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/verify_repo.py +0 -0
  229. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/verify_rules.py +0 -0
  230. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/version_check.py +0 -0
  231. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/waivers.py +0 -0
  232. {sourcecode-5.3.0 → sourcecode-5.3.1}/src/sourcecode/workspace.py +0 -0
  233. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/functions/README.md +0 -0
  234. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/functions/get-license/index.ts +0 -0
  235. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  236. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/functions/telemetry/index.ts +0 -0
  237. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/sql/license_event_ordering.sql +0 -0
  238. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/sql/licensing_schema.sql +0 -0
  239. {sourcecode-5.3.0 → sourcecode-5.3.1}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,58 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.3.1] — 2026-08-11
6
+
7
+ **Six fixes from one field battery.** 5.3.0 was run over the 14 repositories in
8
+ the field corpus — 22 commands each, plus the symbol-level commands and the
9
+ edge-input battery. No command crashed on its own account: every non-zero exit
10
+ was a well-formed input error. What the battery found instead is the class this
11
+ series keeps returning to — two surfaces answering one question with two
12
+ numbers, and a count published under the name of a population it does not count.
13
+
14
+ ### Fixed
15
+ - **One admission authority for the route population.** `extract_java_endpoints`
16
+ filtered its Java files with `is_test_path` alone, while `find_java_files` —
17
+ which feeds the IR and therefore `spring-audit`, `impact`, `validation` and
18
+ `data-exposure` — also drops the `src/main` of a module that is itself test
19
+ infrastructure. Measured on jobrunr: `endpoints` published 3 annotation routes
20
+ from `quarkus-jobrunr-tests` that `spring-audit` could not see at all, 3
21
+ against `endpoints_analyzed: 0`, under the payload's own
22
+ `semantic_model_endpoints_note` declaring the two counts to be one figure. The
23
+ rule is now `repository_ir.is_test_source_file`, called by both builders
24
+ (jobrunr 14/3 → 12/0; every other repository in the corpus unchanged).
25
+ - **A call site is an HTTP call only where an HTTP client is.** The Java
26
+ consumer-join matcher admitted `<receiver>.put|patch|delete|exchange("…")` on
27
+ any receiver — HTTP verbs, and three of the most common method names in Java.
28
+ Measured on eureka joined against open-banking-gateway: all 20 rows of
29
+ `calls_unresolved` were `Map.put` literals in one file, and a
30
+ `map.put("/x", v)` would have been published as a consumer calling `/x` — the
31
+ direction the module declares decisive. A file must now import or declare an
32
+ HTTP client type before its call sites are read.
33
+ - **A snapshot that measured nothing reports unknown, not zero.**
34
+ `update_ris_api_surface` / `update_ris_spring_audit` write a stub snapshot so
35
+ their own section survives when no full one exists; `cold-start` read that stub
36
+ as a measurement. On alfresco-community-repo — 7 000+ Java files, 901
37
+ WebScripts — it answered `validation: {spring_detected: false,
38
+ controllers_found: 0, status: "valid"}` with every section empty. Now
39
+ `status: cold_start_incomplete`, `validation.status: empty_snapshot`, every
40
+ count `null`, with a statement naming what is unknown and how to build the
41
+ snapshot. A repository measured to have no Spring surface still reports its
42
+ zeroes — that is a fact.
43
+ - **The scale signal counts files, and says files.** The headline sentence of
44
+ every Java summary ended with "N Java classes" while counting `.java` paths.
45
+ BroadleafCommerce is 2 659 Java files and 3 032 declared types, so the label
46
+ contradicted `repo-ir.analysis_meta.classes` in the same run.
47
+ - **The human audit bundle prints counts, not Python objects.** The two
48
+ breakdown lines of the markdown report interpolated their dicts directly, so
49
+ the signed evidence bundle published `- Endpoint access: {'no_rule_matched':
50
+ 676}` on keycloak, and a literal `None` for a section that was never measured.
51
+ - **Bookkeeping never takes down a finished analysis.** `RunRecord.write` had no
52
+ guard on its terminal write, so a run directory that disappeared during the
53
+ analysis — a concurrent `ask cache clear`, a tmp reaper, an evicted cache —
54
+ raised `FileNotFoundError` out of the analysis scope's exit. Observed on
55
+ `audit-report`, which died with a traceback having already composed the report.
56
+
5
57
  ## [5.3.0] — 2026-08-11
6
58
 
7
59
  **The capability queue, built on the stable point.** 5.2.0 was the highest-scored
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.3.0
3
+ Version: 5.3.1
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-5.3.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-5.3.1-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -126,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
126
126
  # pip / pipx
127
127
  pipx install sourcecode # or: pip install sourcecode
128
128
 
129
- ask version # ask 5.3.0 — and, on a build that has aged,
129
+ ask version # ask 5.3.1 — and, on a build that has aged,
130
130
  # how many releases have probably shipped since
131
131
  ```
132
132
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
6
6
 
7
- ![Version](https://img.shields.io/badge/version-5.3.0-blue)
7
+ ![Version](https://img.shields.io/badge/version-5.3.1-blue)
8
8
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
9
9
 
10
10
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -88,7 +88,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
88
88
  # pip / pipx
89
89
  pipx install sourcecode # or: pip install sourcecode
90
90
 
91
- ask version # ask 5.3.0 — and, on a build that has aged,
91
+ ask version # ask 5.3.1 — and, on a build that has aged,
92
92
  # how many releases have probably shipped since
93
93
  ```
94
94
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "5.3.0"
7
+ version = "5.3.1"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "5.3.0"
7
+ __version__ = "5.3.1"
@@ -147,6 +147,23 @@ def build_audit_report(
147
147
  return normalized
148
148
 
149
149
 
150
+ def _render_breakdown(value: Any) -> str:
151
+ """A count-per-class map, written the way a reader of a report reads one.
152
+
153
+ The two breakdown lines were interpolated as Python objects, so the signed
154
+ human bundle published `{'no_rule_matched': 676}` on keycloak — and a literal
155
+ `None` for a section that was not measured at all, which is the one thing a
156
+ report of record must never say when it means "not measured".
157
+ """
158
+ if value is None:
159
+ return "not measured"
160
+ if isinstance(value, dict):
161
+ if not value:
162
+ return "none reported"
163
+ return ", ".join(f"`{k}` {v}" for k, v in sorted(value.items()))
164
+ return f"`{value}`"
165
+
166
+
150
167
  def render_markdown(report: dict[str, Any]) -> str:
151
168
  """Human report renderer over the JSON contract."""
152
169
  summary = report.get("summary") or {}
@@ -160,8 +177,8 @@ def render_markdown(report: dict[str, Any]) -> str:
160
177
  f"- Profile set: `{profile}`",
161
178
  f"- Total defects: `{read_count(summary, 'total_defects')}`"
162
179
  + (" **(floor — this run was cut short)**" if summary.get("partial") else ""),
163
- f"- Risk bands: `{summary.get('risk_bands')}`",
164
- f"- Endpoint access: `{summary.get('endpoint_access')}`",
180
+ f"- Risk bands: {_render_breakdown(summary.get('risk_bands'))}",
181
+ f"- Endpoint access: {_render_breakdown(summary.get('endpoint_access'))}",
165
182
  "",
166
183
  "## Top Risks",
167
184
  "",
@@ -13240,7 +13240,10 @@ def cold_start_cmd(
13240
13240
  """Output Repository Intelligence Snapshot bootstrap context as JSON.
13241
13241
 
13242
13242
  Returns instantly from persisted RIS — zero re-analysis cost.
13243
- status: cold_start_ready | cold_start_stale | no_ris
13243
+ status: cold_start_ready | cold_start_stale | cold_start_incomplete | no_ris
13244
+ `cold_start_incomplete` means the snapshot is missing sections (or holds no
13245
+ measurement at all — `validation.status: empty_snapshot`, every count
13246
+ `null`): unknown, never zero.
13244
13247
 
13245
13248
  \b
13246
13249
  Note: Full output is large (~100K–200K tokens for medium repos).
@@ -71,6 +71,38 @@ _METHOD_ATTR = re.compile(r"RequestMethod\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIO
71
71
  #: annotation-shaped path on a server controller is a route, not a call.
72
72
  _DECLARATIVE_CLIENT = re.compile(r"@(\w*FeignClient|HttpExchange|RegisterRestClient)\b")
73
73
 
74
+ #: `put`, `patch` and `delete` are HTTP verbs and they are also three of the most
75
+ #: common method names in Java: `map.put("us-east-1", "us-east-1a")` matches the
76
+ #: call shape exactly. Measured on eureka, joined against open-banking-gateway:
77
+ #: all 20 "unresolved client calls" were `Map.put` literals in
78
+ #: `AbstractAzToRegionMapper`, and a `map.put("/x", v)` would have been published
79
+ #: as a real consumer call to `/x` — the direction this module declares decisive.
80
+ #: So the shape alone does not admit a call site: the FILE must carry evidence
81
+ #: that an HTTP client is in it. These are the published type names of the HTTP
82
+ #: client libraries themselves, matched as a declaration or an import — evidence,
83
+ #: never a rule about the calling code's own naming.
84
+ _HTTP_CLIENT_TYPE = re.compile(
85
+ r"\b(?:Async|Test|OAuth2)?RestTemplate\b"
86
+ r"|\bWeb(?:Test)?Client\b"
87
+ r"|\bRestClient\b"
88
+ r"|\bOkHttpClient\b"
89
+ r"|\bHttpClient\b"
90
+ r"|\bClientHttpRequest\w*\b"
91
+ r"|\bResteasyClient\b"
92
+ r"|\bjakarta\.ws\.rs\.client\b|\bjavax\.ws\.rs\.client\b"
93
+ r"|\bWebResource\b|\bWebTarget\b"
94
+ r"|\bRequestSpecification\b" # rest-assured
95
+ )
96
+
97
+
98
+ def _file_declares_http_client(text: str) -> bool:
99
+ """True when this file imports or declares a type that speaks HTTP as a client.
100
+
101
+ A file with no such type has no `restTemplate` to call, so a `.put("…")` in it
102
+ is some other API with the same method name.
103
+ """
104
+ return bool(_HTTP_CLIENT_TYPE.search(text) or _DECLARATIVE_CLIENT.search(text))
105
+
74
106
  _JAVA_SKIP_DIRS = frozenset({
75
107
  "target", "build", "out", ".git", "node_modules", ".gradle",
76
108
  })
@@ -103,7 +135,10 @@ def extract_java_client_calls(base: Path) -> "tuple[list[ClientCall], list[dict]
103
135
  except OSError:
104
136
  continue
105
137
  rel = path.relative_to(base).as_posix()
106
- for match in _JAVA_TEMPLATE_CALL.finditer(text):
138
+ _http_client_file = _file_declares_http_client(text)
139
+ for match in (
140
+ _JAVA_TEMPLATE_CALL.finditer(text) if _http_client_file else ()
141
+ ):
107
142
  op = match.group("op").lower()
108
143
  verb = next((v for k, v in _VERB_FROM_OP.items() if op.startswith(k)), "")
109
144
  if not verb and op == "exchange":
@@ -6173,6 +6173,56 @@ def _recover_openapi_spec_routes(
6173
6173
  _SEMANTIC_MODEL_ENDPOINT_SOURCES: frozenset[str] = frozenset({"annotation", "openapi-spec"})
6174
6174
 
6175
6175
 
6176
+ def is_test_source_file(rel_path: str) -> bool:
6177
+ """Return True when *rel_path* is a test source and must not enter any surface.
6178
+
6179
+ One authority for the question both Java file-list builders ask —
6180
+ `find_java_files` (which feeds the IR and therefore every CIR consumer:
6181
+ spring-audit, impact, validation, data-exposure) and
6182
+ `extract_java_endpoints` (which feeds the `endpoints` command). They asked it
6183
+ separately until a field run on jobrunr made the divergence visible:
6184
+ `endpoints` published 3 annotation routes from
6185
+ `quarkus-jobrunr-tests/src/main/java/org/jobrunr/quarkus/it/` that
6186
+ `spring-audit` could not see at all, while the payload's own
6187
+ `semantic_model_endpoints_note` claims the two counts are the same figure —
6188
+ 3 against `endpoints_analyzed: 0`.
6189
+
6190
+ Two facts, and C1-28 is why they are written as two.
6191
+ `is_test_path` answers "is this file a test source"; since the authority took
6192
+ over it never fires inside a declared main source root, which is correct
6193
+ everywhere except inside a module that is itself test infrastructure — a
6194
+ `test-framework` module has no product for its `src/main` to belong to. There
6195
+ the weaker question is the right one: does this file look like a test by name
6196
+ or by package. Both come from the one authority, so this stays a composition
6197
+ of published facts rather than a third rule.
6198
+
6199
+ A test *fixture* module (`*-test-utils`) is deliberately NOT excluded here:
6200
+ its endpoints are kept as raw evidence and tagged `scope: test_util`
6201
+ downstream, by both surfaces.
6202
+ """
6203
+ rel = rel_path.replace("\\", "/").lstrip("/")
6204
+ if _is_test_path(rel):
6205
+ return True
6206
+ _rel_sl = "/" + rel # leading slash: the check below is prefix-safe
6207
+ if "/src/main/" in _rel_sl and _matches_test_naming(rel):
6208
+ _prefix = _rel_sl.split("/src/main/")[0]
6209
+ _prefix_parts = [p for p in _prefix.split("/") if p]
6210
+ # A module is a test module if is_test_path says so OR if any module
6211
+ # directory component starts with "test" (e.g. "test-framework",
6212
+ # "test-providers", "testsuite") OR contains "test"/"tests" as a
6213
+ # hyphen/underscore-separated word (e.g. "jobrunr-micronaut-tests").
6214
+ return (
6215
+ _is_test_path(_prefix + "/x.java")
6216
+ or any(p.lower().startswith("test") for p in _prefix_parts)
6217
+ or any(
6218
+ w in {"test", "tests", "spec", "specs"}
6219
+ for p in _prefix_parts
6220
+ for w in p.lower().replace("-", " ").replace("_", " ").split()
6221
+ )
6222
+ )
6223
+ return False
6224
+
6225
+
6176
6226
  # ---------------------------------------------------------------------------
6177
6227
  # Convenience: find Java files in a repo
6178
6228
  # ---------------------------------------------------------------------------
@@ -6190,7 +6240,7 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
6190
6240
  """
6191
6241
  import re as _re
6192
6242
  from typing import Any as _Any
6193
- from sourcecode.path_filters import is_test_path, is_test_fixture_module_path
6243
+ from sourcecode.path_filters import is_test_fixture_module_path
6194
6244
 
6195
6245
  _EXTENDS_FROM_SIG = _re.compile(r'\bextends\s+(\w+)')
6196
6246
 
@@ -6210,9 +6260,20 @@ def extract_java_endpoints(root: Path) -> "dict[str, Any]":
6210
6260
  _CLIENT_PATH_FRAGMENTS = (
6211
6261
  "/admin-client/", "/rest-client/", "/client-api/", "/api-client/",
6212
6262
  )
6263
+ # Admission is `is_test_source_file`, the same authority `find_java_files`
6264
+ # uses, so this command's route population and the IR's cannot diverge:
6265
+ # `is_test_path` alone let a test MODULE's `src/main` resources through here
6266
+ # while the IR dropped them, and the two published counts then contradicted
6267
+ # each other (jobrunr: 3 annotation routes vs `endpoints_analyzed: 0`).
6268
+ def _rel(p: Path) -> str:
6269
+ try:
6270
+ return str(p.relative_to(root)).replace("\\", "/")
6271
+ except ValueError:
6272
+ return str(p).replace("\\", "/")
6273
+
6213
6274
  java_files = sorted(
6214
6275
  p for p in root.rglob("*.java")
6215
- if not is_test_path(str(p).replace("\\", "/"))
6276
+ if not is_test_source_file(_rel(p))
6216
6277
  and "target/" not in str(p).replace("\\", "/")
6217
6278
  and not any(f in str(p).replace("\\", "/") for f in _CLIENT_PATH_FRAGMENTS)
6218
6279
  )
@@ -6841,43 +6902,9 @@ def find_java_files(
6841
6902
  except ValueError:
6842
6903
  continue
6843
6904
  parts = rel.split("/")
6844
- # Skip test dirs — use centralised is_test_path (consistent with
6845
- # extract_java_endpoints), guarded against false positives where a Java
6846
- # *package* is named "test" inside a production src/main/ source root.
6847
- if not include_tests:
6848
- # Two facts, and C1-28 is why they are now written as two.
6849
- # `is_test_path` answers "is this file a test source"; since the
6850
- # authority took over it never fires inside a declared main source
6851
- # root, which is correct everywhere except inside a module that is
6852
- # itself test infrastructure — a `test-framework` module has no
6853
- # product for its `src/main` to belong to. There the weaker question
6854
- # is the right one: does this file look like a test by name or by
6855
- # package. Both come from the one authority, so this stays a
6856
- # composition of published facts rather than a third rule. A test
6857
- # *fixture* module (`*-test-utils`) is deliberately NOT excluded
6858
- # here: its endpoints are kept as raw evidence and tagged
6859
- # `scope: test_util` downstream.
6860
- _rel_sl = "/" + rel # leading slash: the checks below are prefix-safe
6861
- if _is_test_path(rel):
6862
- continue
6863
- if "/src/main/" in _rel_sl and _matches_test_naming(rel):
6864
- _prefix = _rel_sl.split("/src/main/")[0]
6865
- _prefix_parts = [p for p in _prefix.split("/") if p]
6866
- # A module is a test module if is_test_path says so OR if any
6867
- # module directory component starts with "test" (e.g. "test-framework",
6868
- # "test-providers", "testsuite") OR contains "test"/"tests" as a
6869
- # hyphen/underscore-separated word (e.g. "jobrunr-micronaut-tests").
6870
- _prefix_is_test = (
6871
- _is_test_path(_prefix + "/x.java")
6872
- or any(p.lower().startswith("test") for p in _prefix_parts)
6873
- or any(
6874
- w in {"test", "tests", "spec", "specs"}
6875
- for p in _prefix_parts
6876
- for w in p.lower().replace("-", " ").replace("_", " ").split()
6877
- )
6878
- )
6879
- if _prefix_is_test:
6880
- continue
6905
+ # Skip test dirs — one authority, shared with extract_java_endpoints.
6906
+ if not include_tests and is_test_source_file(rel):
6907
+ continue
6881
6908
  # Skip vendor/generated/build dirs
6882
6909
  if any(part in _VENDOR_DIRS for part in parts[:-1]):
6883
6910
  continue
@@ -484,6 +484,20 @@ def _has_uncommitted_changes(repo_root: Path) -> bool:
484
484
  return bool(worktree_dirty(root, ignore=own))
485
485
 
486
486
 
487
+ def _compact_probe(section: object) -> bool:
488
+ """True when an RIS section holds something a reader could act on.
489
+
490
+ A stub writes `{"endpoints": [], "controllers_index": []}` — a truthy dict
491
+ that contains no measurement, so the section's emptiness has to be asked of
492
+ its values, not of the container.
493
+ """
494
+ if isinstance(section, dict):
495
+ return any(_compact_probe(v) for v in section.values())
496
+ if isinstance(section, (list, tuple, set)):
497
+ return bool(section)
498
+ return section not in (None, "", 0, False)
499
+
500
+
487
501
  def get_cold_start_context(repo_root: Path) -> dict:
488
502
  """Return a lightweight bootstrap object from the persisted RIS.
489
503
 
@@ -527,22 +541,54 @@ def get_cold_start_context(repo_root: Path) -> dict:
527
541
  _endpoints_found = len(endpoints)
528
542
  # Spring is detected when controllers exist in structural map or api surface.
529
543
  _spring_detected = bool(_controllers_found) or bool(_controllers_in_api)
530
- _validation_status = (
531
- "incomplete_snapshot"
532
- if _is_java and _spring_detected and _endpoints_found == 0
533
- else "valid"
544
+ # A stub snapshot measured nothing. `update_ris_api_surface` /
545
+ # `update_ris_spring_audit` write one (`partial: True`, `confidence: 0.0`,
546
+ # `git_head: ""`) so their own section survives when no full snapshot
547
+ # exists yet — and on a repository whose Spring surface is genuinely
548
+ # empty, that stub is all there is. Measured on
549
+ # alfresco-community-repo (7 000+ Java files, 901 WebScripts): every
550
+ # section empty, and `validation: {spring_detected: false,
551
+ # controllers_found: 0, status: "valid"}` — an answer a bootstrapping
552
+ # agent reads as "this repository has no controllers and no entry
553
+ # points". Nothing was measured, so nothing is being reported: the
554
+ # sections are unknown, not zero.
555
+ _nothing_measured = not (
556
+ _compact_probe(ris.compact_summary)
557
+ or _compact_probe(ris.structural_map)
558
+ or _controllers_found
559
+ or _endpoints_found
560
+ or _compact_probe(ris.git_context_snapshot)
534
561
  )
562
+ if _nothing_measured:
563
+ _validation_status = "empty_snapshot"
564
+ elif _is_java and _spring_detected and _endpoints_found == 0:
565
+ _validation_status = "incomplete_snapshot"
566
+ else:
567
+ _validation_status = "valid"
535
568
  _validation: dict = {
536
- "spring_detected": _spring_detected,
537
- "controllers_found": _controllers_found,
538
- "endpoints_found": _endpoints_found,
569
+ "spring_detected": None if _nothing_measured else _spring_detected,
570
+ "controllers_found": None if _nothing_measured else _controllers_found,
571
+ "endpoints_found": None if _nothing_measured else _endpoints_found,
539
572
  "status": _validation_status,
540
573
  }
574
+ if _nothing_measured:
575
+ _validation["statement"] = (
576
+ "This snapshot holds no measurement: it was written by a command "
577
+ "that persisted its own section only. Every field above is unknown "
578
+ "— not zero, and not evidence that the repository has no controllers, "
579
+ "endpoints or entry points. Build one with `ask cache warm <path>` "
580
+ "(or any analysis command) and re-run."
581
+ )
541
582
 
542
583
  # When the snapshot is structurally incomplete, downgrade status so agents
543
584
  # don't assume cold_start_ready when critical sections are missing.
544
585
  _status_base = "cold_start_stale" if stale else "cold_start_ready"
545
- if _validation_status == "incomplete_snapshot" and not stale:
586
+ if _validation_status in ("incomplete_snapshot", "empty_snapshot") and not stale:
587
+ _status_base = "cold_start_incomplete"
588
+ if _validation_status == "empty_snapshot":
589
+ # A stub carries no head, so the staleness comparison it loses is not
590
+ # the fact worth reporting: `cold_start_stale` would send a reader to
591
+ # re-run against a newer tree, when what is missing is the snapshot.
546
592
  _status_base = "cold_start_incomplete"
547
593
 
548
594
  _compact = ris.compact_summary or {}
@@ -119,9 +119,28 @@ class RunRecord:
119
119
  payload["output_size_bytes"] = output_size
120
120
  if error:
121
121
  payload["error"] = error
122
+ # Bookkeeping never takes down an answer. `RunRecord.create` already
123
+ # treats an unwritable directory as "no record"; the terminal write did
124
+ # not, so a run directory that disappeared *during* the analysis — a
125
+ # concurrent `ask cache clear`, a tmp reaper, an evicted cache — raised
126
+ # FileNotFoundError out of the `_expensive_analysis_scope` exit, after the
127
+ # analysis had completed. Observed: `ask audit-report` on langchain4j died
128
+ # with a traceback over a missing `runs/<id>.tmp`, having already built
129
+ # the report. The record is recovery metadata; the answer is the product.
130
+ blob = json.dumps(payload, indent=2, sort_keys=True)
122
131
  tmp = self.path.with_suffix(".tmp")
123
- tmp.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
124
- tmp.replace(self.path)
132
+ for attempt in (0, 1):
133
+ try:
134
+ tmp.write_text(blob, encoding="utf-8")
135
+ tmp.replace(self.path)
136
+ return
137
+ except OSError:
138
+ if attempt:
139
+ return
140
+ try:
141
+ self.path.parent.mkdir(parents=True, exist_ok=True)
142
+ except OSError:
143
+ return
125
144
 
126
145
 
127
146
  def classify_run(path: Path) -> dict[str, Any]:
@@ -389,14 +389,20 @@ class ProjectSummarizer:
389
389
  parts.append(f"Domains: {', '.join(domains)}")
390
390
 
391
391
  # Quantitative structural suffix for Java projects — adds concrete scale signals
392
- # that README descriptions omit (class count, transactional boundary count).
392
+ # that README descriptions omit (file count, transactional boundary count).
393
393
  if primary is not None and primary.stack.lower() == "java":
394
394
  quant_parts: list[str] = []
395
395
  java_files = sum(
396
396
  1 for p in sm.file_paths if p.endswith(".java")
397
397
  )
398
398
  if java_files >= 50:
399
- quant_parts.append(f"{java_files:,} Java classes")
399
+ # This counts files, and says so. It read "N Java classes" — the
400
+ # headline sentence of every summary, naming a population the tool
401
+ # measures separately and differently: BroadleafCommerce is 2 659
402
+ # Java files and 3 032 declared types (2 174 classes, 742
403
+ # interfaces), so the label was wrong by a nested type per file
404
+ # and contradicted `repo-ir.analysis_meta.classes`.
405
+ quant_parts.append(f"{java_files:,} Java files")
400
406
  txn_classes: list[str] = []
401
407
  for stack in non_tooling_stacks:
402
408
  txn_classes.extend(getattr(stack, "transactional_classes", []))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes