sourcecode 4.10.3__tar.gz → 4.10.5__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 (220) hide show
  1. {sourcecode-4.10.3 → sourcecode-4.10.5}/CHANGELOG.md +122 -0
  2. {sourcecode-4.10.3 → sourcecode-4.10.5}/PKG-INFO +6 -6
  3. {sourcecode-4.10.3 → sourcecode-4.10.5}/README.md +2 -2
  4. {sourcecode-4.10.3 → sourcecode-4.10.5}/pyproject.toml +4 -4
  5. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architectural_baseline.py +34 -5
  7. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cache_model.py +41 -1
  8. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cli.py +121 -18
  9. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pr_impact.py +38 -4
  10. sourcecode-4.10.5/src/sourcecode/progress.py +218 -0
  11. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/provenance.py +18 -10
  12. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/release_info.py +1 -1
  13. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/repository_ir.py +38 -1
  14. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_config_scan.py +30 -22
  15. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_security_audit.py +10 -0
  16. sourcecode-4.10.5/src/sourcecode/text_input.py +93 -0
  17. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_edit.py +31 -0
  18. sourcecode-4.10.3/src/sourcecode/progress.py +0 -125
  19. {sourcecode-4.10.3 → sourcecode-4.10.5}/.github/workflows/build-windows.yml +0 -0
  20. {sourcecode-4.10.3 → sourcecode-4.10.5}/.gitignore +0 -0
  21. {sourcecode-4.10.3 → sourcecode-4.10.5}/.ruff.toml +0 -0
  22. {sourcecode-4.10.3 → sourcecode-4.10.5}/CLAUDE.md +0 -0
  23. {sourcecode-4.10.3 → sourcecode-4.10.5}/CONTRIBUTING.md +0 -0
  24. {sourcecode-4.10.3 → sourcecode-4.10.5}/LICENSE +0 -0
  25. {sourcecode-4.10.3 → sourcecode-4.10.5}/SECURITY.md +0 -0
  26. {sourcecode-4.10.3 → sourcecode-4.10.5}/raw +0 -0
  27. {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/compare_integration_engines.py +0 -0
  28. {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/customer_smoke_test.sh +0 -0
  29. {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/generate_jdk_exports.py +0 -0
  30. {sourcecode-4.10.3 → sourcecode-4.10.5}/scripts/perf_harness.py +0 -0
  31. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/adaptive_scanner.py +0 -0
  32. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/archetype.py +0 -0
  33. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architectural_delta.py +0 -0
  34. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architecture_analyzer.py +0 -0
  35. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/architecture_summary.py +0 -0
  36. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ast_extractor.py +0 -0
  37. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/audit_report.py +0 -0
  38. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/baseline_autocapture.py +0 -0
  39. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cache.py +0 -0
  40. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/call_surface.py +0 -0
  41. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/caller_metrics.py +0 -0
  42. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/caller_reach.py +0 -0
  43. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/canonical_ir.py +0 -0
  44. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/chain_rules.py +0 -0
  45. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/change_plan.py +0 -0
  46. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/cir_graphs.py +0 -0
  47. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/classifier.py +0 -0
  48. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/client_calls.py +0 -0
  49. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/code_notes_analyzer.py +0 -0
  50. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/compare.py +0 -0
  51. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/confidence_analyzer.py +0 -0
  52. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/constraint_diff.py +0 -0
  53. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_cache.py +0 -0
  54. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_graph.py +0 -0
  55. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_scorer.py +0 -0
  56. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/context_summarizer.py +0 -0
  57. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_diff.py +0 -0
  58. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_init.py +0 -0
  59. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_model.py +0 -0
  60. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/contract_pipeline.py +0 -0
  61. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/coverage_parser.py +0 -0
  62. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/data_exposure.py +0 -0
  63. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/data_labels.py +0 -0
  64. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/defect_identity.py +0 -0
  65. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/degradation.py +0 -0
  66. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/dependency_analyzer.py +0 -0
  67. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/deployment_prefix.py +0 -0
  68. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/__init__.py +0 -0
  69. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/base.py +0 -0
  70. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/csproj_parser.py +0 -0
  71. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/dart.py +0 -0
  72. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/dotnet.py +0 -0
  73. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/elixir.py +0 -0
  74. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/go.py +0 -0
  75. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/heuristic.py +0 -0
  76. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/hybrid.py +0 -0
  77. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/java.py +0 -0
  78. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/jvm_ext.py +0 -0
  79. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/nodejs.py +0 -0
  80. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/parsers.py +0 -0
  81. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/php.py +0 -0
  82. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/project.py +0 -0
  83. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/python.py +0 -0
  84. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/ruby.py +0 -0
  85. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/rust.py +0 -0
  86. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/systems.py +0 -0
  87. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/terraform.py +0 -0
  88. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/detectors/tooling.py +0 -0
  89. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/doc_analyzer.py +0 -0
  90. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/dynamic_argument_surface.py +0 -0
  91. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/endpoint_literals.py +0 -0
  92. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/endpoint_metrics.py +0 -0
  93. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/entrypoint_classifier.py +0 -0
  94. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/env_analyzer.py +0 -0
  95. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/envelope.py +0 -0
  96. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/environment_resolution.py +0 -0
  97. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/error_schema.py +0 -0
  98. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/evidence_provider.py +0 -0
  99. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/explain.py +0 -0
  100. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/facts/__init__.py +0 -0
  101. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/facts/registry.json +0 -0
  102. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/file_chunker.py +0 -0
  103. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/file_classifier.py +0 -0
  104. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/filter_surface.py +0 -0
  105. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/format_contract.py +0 -0
  106. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/fqn_utils.py +0 -0
  107. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/git_analyzer.py +0 -0
  108. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/git_checkout.py +0 -0
  109. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/graph_analyzer.py +0 -0
  110. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/graph_evidence.py +0 -0
  111. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/hibernate_strat.py +0 -0
  112. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/identity_fallback.py +0 -0
  113. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/integration_coordinates.py +0 -0
  114. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/jdk_exports.py +0 -0
  115. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/license.py +0 -0
  116. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/__init__.py +0 -0
  117. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  118. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  119. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  120. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  121. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  122. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/orchestrator.py +0 -0
  123. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/registry.py +0 -0
  124. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/runner.py +0 -0
  125. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp/server.py +0 -0
  126. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/mcp_nudge.py +0 -0
  127. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/metrics_analyzer.py +0 -0
  128. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/migrate_check.py +0 -0
  129. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/migration_blast.py +0 -0
  130. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/non_coverage.py +0 -0
  131. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/openapi_surface.py +0 -0
  132. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/openrewrite_recipe.py +0 -0
  133. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/output_budget.py +0 -0
  134. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/parse_cache.py +0 -0
  135. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/path_admission.py +0 -0
  136. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/path_filters.py +0 -0
  137. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/perf.py +0 -0
  138. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pipe_contract.py +0 -0
  139. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/posture.py +0 -0
  140. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/pr_comment_renderer.py +0 -0
  141. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/prepare_context.py +0 -0
  142. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ranking_engine.py +0 -0
  143. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/readiness_timeline.py +0 -0
  144. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/reconciliation.py +0 -0
  145. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/redactor.py +0 -0
  146. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/reference_facts.py +0 -0
  147. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/relevance_scorer.py +0 -0
  148. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/remedies.py +0 -0
  149. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/rename_refactor.py +0 -0
  150. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/repo_classifier.py +0 -0
  151. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/__init__.py +0 -0
  152. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/context.py +0 -0
  153. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/errors.py +0 -0
  154. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/executor.py +0 -0
  155. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/planner.py +0 -0
  156. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/query.py +0 -0
  157. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/request.py +0 -0
  158. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/resolution.py +0 -0
  159. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/result.py +0 -0
  160. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/retriever.py +0 -0
  161. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/runtime.py +0 -0
  162. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps.py +0 -0
  163. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
  164. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_graph.py +0 -0
  165. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_impact.py +0 -0
  166. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_intf.py +0 -0
  167. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_struct.py +0 -0
  168. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/retrieval/steps_txsec.py +0 -0
  169. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/ris.py +0 -0
  170. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/risk.py +0 -0
  171. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/rule_catalog.py +0 -0
  172. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/runs.py +0 -0
  173. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/runtime_classifier.py +0 -0
  174. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/sarif.py +0 -0
  175. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/scanner.py +0 -0
  176. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/schema.py +0 -0
  177. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
  178. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_config.py +0 -0
  179. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/security_posture.py +0 -0
  180. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_analyzer.py +0 -0
  181. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_impact_engine.py +0 -0
  182. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_integration_engine.py +0 -0
  183. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/semantic_services.py +0 -0
  184. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/serializer.py +0 -0
  185. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/servlet_surface.py +0 -0
  186. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/source_text.py +0 -0
  187. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_event_topology.py +0 -0
  188. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_findings.py +0 -0
  189. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_impact.py +0 -0
  190. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_model.py +0 -0
  191. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_profiles.py +0 -0
  192. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_properties.py +0 -0
  193. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_semantic.py +0 -0
  194. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/spring_tx_analyzer.py +0 -0
  195. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/summarizer.py +0 -0
  196. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/target_admission.py +0 -0
  197. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/__init__.py +0 -0
  198. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/config.py +0 -0
  199. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/consent.py +0 -0
  200. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/events.py +0 -0
  201. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/filters.py +0 -0
  202. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/telemetry/transport.py +0 -0
  203. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/test_gap_ranking.py +0 -0
  204. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/test_sources.py +0 -0
  205. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/token_estimate.py +0 -0
  206. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/tree_utils.py +0 -0
  207. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/type_usage_surface.py +0 -0
  208. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/validation_inference.py +0 -0
  209. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/validation_surface.py +0 -0
  210. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_repo.py +0 -0
  211. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/verify_rules.py +0 -0
  212. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/version_check.py +0 -0
  213. {sourcecode-4.10.3 → sourcecode-4.10.5}/src/sourcecode/workspace.py +0 -0
  214. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/README.md +0 -0
  215. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/get-license/index.ts +0 -0
  216. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
  217. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/functions/telemetry/index.ts +0 -0
  218. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/license_event_ordering.sql +0 -0
  219. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/licensing_schema.sql +0 -0
  220. {sourcecode-4.10.3 → sourcecode-4.10.5}/supabase/sql/telemetry_events.sql +0 -0
@@ -2,6 +2,128 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [4.10.5] — 2026-08-07
6
+
7
+ Second release of the field-evaluation #13 queue. Three rows close and one closes
8
+ in part; the suite is 6 691 tests green.
9
+
10
+ ⚠ **Two behaviour changes to read before upgrading a pipeline.** Both are the fix
11
+ working, and both can move a gate:
12
+
13
+ - On Windows, `pr-impact --files -` now resolves paths it previously mangled, so a
14
+ pipeline that was passing because its diff decoded to nothing can start failing —
15
+ with the verdict the diff always deserved (C3-58).
16
+ - A security finding that existed only in build output (`target/classes`) is no
17
+ longer reported, so a repository audited mid-build can report fewer findings
18
+ (C3-64).
19
+
20
+ ### Performance
21
+
22
+ - **The security configuration scan discovers its files 8.7× faster** (C3-64):
23
+ 0.61 s → 0.07 s on keycloak, 0.45 s → 0.05 s on alfresco. Five `rglob` calls
24
+ became one pruned walk. `rglob` cannot skip a subtree, so the old cost was set by
25
+ the build output and the VCS metadata rather than by the source: 48.4 % of
26
+ openmrs-core's tree entries and 53.3 % of this repository's live under directories
27
+ that hold no source at all. On a clean checkout the end-to-end scan moves 1.73 s →
28
+ 1.43 s (the rest is reading Java sources); the win grows with how built the tree
29
+ is, which is the state a developer's repository is normally in.
30
+
31
+ ### Fixed
32
+
33
+ - **A path list is decoded by its bytes, never by the platform locale** (C3-58).
34
+ `ask pr-impact --files -` read stdin through Python's locale decoding, which on
35
+ Windows is the ANSI codepage: `git diff --name-only | ask pr-impact --files -`
36
+ returned `risk: UNKNOWN` and 0 endpoints where the same list without the shell's
37
+ default byte-order mark returned `CRITICAL` and 1 144 — a green pipeline on the
38
+ gating command. The BOM was the visible half; `AñoService.java` arriving as
39
+ `AñoService.java` was the cause. The new authority `sourcecode/text_input.py`
40
+ reads `sys.stdin.buffer` and lets the bytes name the encoding, and the list-file
41
+ shape goes through it too: PowerShell 5.1 writes UTF-16-LE by default, which the
42
+ previous UTF-8 read raised on. Undecodable bytes now yield an unusable path with
43
+ encoding named as a cause, never a traceback.
44
+ - **No command claims `done` before its answer exists** (C3-54, part 2). The
45
+ completion line moved out of `Progress.finish()` entirely: `finish()` claims the
46
+ work is done, and the line is printed when the answer has actually been handed
47
+ over. A run killed in between prints nothing, and a run that raised prints
48
+ nothing — previously ~20 commands emitted `done (Xs)` while their `--output` file
49
+ did not exist yet, which in the field meant 6.8 minutes of analysis reported as
50
+ success over an absent file. Output files are also durable now: the shared writer
51
+ fsyncs data and directory entry before anything reports a write, and `repo-ir` —
52
+ the one `-o` path that bypassed that writer — goes through it.
53
+ - **A rejected progress write no longer silences progress for the rest of the run**
54
+ (C3-56, part). The heartbeat treated any write failure as terminal; it now retries
55
+ and gives up only after five consecutive failures. The reported 393 s of silence
56
+ did not reproduce: the cadence is timer-driven and measured exact on a cold
57
+ 29.9 s run (5.0 / 10.0 / 15.0 / 20.0 / 25.1 s). The progress denominator and ETA
58
+ the row also asks for are still open.
59
+ - **A build copy is no longer a second security finding** (C3-64). The configuration
60
+ scan walked the whole tree five times and could not skip a subtree, so a compiled
61
+ copy of `application.properties` under `target/classes` was reported beside the
62
+ source file it came from. It now walks once, pruning build output and VCS metadata
63
+ with the same ignore set Java discovery uses.
64
+ - **An unresolved changed file names the right thing to check** (C3-58). One message
65
+ covered both causes and pointed at the wrong one: a path that is *not on disk* is a
66
+ path problem (form and encoding), while a path that *is* there but maps to no class
67
+ is a fact about the file. They now say so separately.
68
+
69
+ ## [4.10.4] — 2026-08-07
70
+
71
+ First release of the queue opened by field evaluation #13. Four of its rows close,
72
+ one closes largely, and the suite goes green: `master` was carrying four failing
73
+ tests before this release, none of them noticed by the release that shipped over
74
+ them.
75
+
76
+ ### Fixed
77
+
78
+ - **`baseline` no longer emits a JSON object keyed by symbol** (C3-62). PowerShell's
79
+ `ConvertFrom-Json` folds object keys case-insensitively, so two Java symbols
80
+ differing only by case — legal, Java is case-sensitive — collided and aborted
81
+ parsing of the *entire* baseline document on the default Windows consumer. `fan_in`
82
+ now serialises as a sorted array of `{symbol, fan_in}`, the shape `hotspots` already
83
+ used. Baselines captured before this release keep diffing and trending: the reader
84
+ accepts both shapes.
85
+ - **`spring-audit` no longer prints `done` before the answer exists** (C3-54, part 1).
86
+ The completion line was emitted inside the `finally` around the analysis, so a
87
+ teardown landing between it and the write left 6.8 minutes of CPU and no output
88
+ file behind a reported success. It now prints after the write it describes. The
89
+ same reorder also stops `done` from being claimed on a run whose analysis *raised*.
90
+ - **`ask cache model` answers in ~830 tokens instead of ~2 590** (C3-63). One line per
91
+ layer and per command by default; the per-command measured timings and notes move
92
+ behind `--full`. Every command and every layer is still named, and the invalidation
93
+ rule is still stated — the density fix is not a coverage cut. `--json` and
94
+ `--markdown` are unchanged.
95
+ - **The evidence manifest publishes how many claims exist, not how many fitted**
96
+ (invariant I-2). The claim builder returned the moment it reached its 200-claim cap,
97
+ so the real total was never computed and the cap was published in its place, with no
98
+ field carrying the truth. `claims_cap` now reads `{total, total_indexed, capped}`
99
+ with `total` measured before truncation. `capped` is also corrected: exactly 200
100
+ claims reported a cut that had not happened.
101
+
102
+ ### Performance
103
+
104
+ - **The `spring-audit` rule pass is 7.8× faster on a large repository** (C3-61):
105
+ 42.1 s → 5.4 s measured on keycloak (5 486 non-test sources, 59 144 symbols), with
106
+ the finding set byte-identical before and after. The cost was not spread across the
107
+ file population — one rule was 94 % of it, and inside it `_proxy_bypass` scanned
108
+ every source for every inferred gate token before establishing that the source never
109
+ mentions the token: 98.9 % of the work was provably empty. The guard is an
110
+ equivalence, not a heuristic, and no threads or processes were added, so output
111
+ determinism is untouched. The rule pass is no longer the dominant phase.
112
+
113
+ ### Tests
114
+
115
+ - The four batteries that were red on `master` before this release are green:
116
+ `test_flag_normalization` compared a wall-clock timestamp between two runs;
117
+ `test_path_admission` had no invocation row for `audit-report`; and
118
+ `test_invariant_counts_from_measurement` failed on `provenance.py` (a real defect,
119
+ fixed above) and on `audit_report.py` (a false positive from a hand-maintained key
120
+ catalogue, now a rule on the key's final token — the audit's anti-vacuous guard is
121
+ extended rather than trusted).
122
+ - Every fix in this release ships a regression battery that was mutation-checked:
123
+ reverting the fix fails the new tests. Two of them were rewritten after the first
124
+ draft passed under that mutation — a summary that exists but is never reached by the
125
+ command, and an error path that exits before the spinner is created.
126
+
5
127
  ## [4.10.3] — 2026-08-05
6
128
 
7
129
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.10.3
3
+ Version: 4.10.5
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
@@ -18,7 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Topic :: Utilities
19
19
  Requires-Python: >=3.9
20
20
  Requires-Dist: defusedxml>=0.7
21
- Requires-Dist: mcp>=1.0.0
21
+ Requires-Dist: mcp<2,>=1.2
22
22
  Requires-Dist: pathspec>=1.0
23
23
  Requires-Dist: ruamel-yaml>=0.18
24
24
  Requires-Dist: tomli>=2.0; python_version < '3.11'
@@ -28,12 +28,12 @@ Requires-Dist: tree-sitter-javascript>=0.21; extra == 'ast'
28
28
  Requires-Dist: tree-sitter-typescript>=0.21; extra == 'ast'
29
29
  Requires-Dist: tree-sitter>=0.21; extra == 'ast'
30
30
  Provides-Extra: dev
31
- Requires-Dist: mcp>=1.0.0; extra == 'dev'
31
+ Requires-Dist: mcp<2,>=1.2; extra == 'dev'
32
32
  Requires-Dist: mypy>=1.10; extra == 'dev'
33
33
  Requires-Dist: pytest>=8; extra == 'dev'
34
34
  Requires-Dist: ruff>=0.15; extra == 'dev'
35
35
  Provides-Extra: mcp
36
- Requires-Dist: mcp>=1.0.0; extra == 'mcp'
36
+ Requires-Dist: mcp<2,>=1.2; extra == 'mcp'
37
37
  Description-Content-Type: text/markdown
38
38
 
39
39
  # ASK Engine
@@ -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-4.10.3-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.10.5-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`**
@@ -106,7 +106,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
106
106
  # pip / pipx
107
107
  pipx install sourcecode # or: pip install sourcecode
108
108
 
109
- ask version # ask 4.10.3 — and, on a build that has aged,
109
+ ask version # ask 4.10.5 — and, on a build that has aged,
110
110
  # how many releases have probably shipped since
111
111
  ```
112
112
 
@@ -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-4.10.3-blue)
7
+ ![Version](https://img.shields.io/badge/version-4.10.5-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`**
@@ -68,7 +68,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
68
68
  # pip / pipx
69
69
  pipx install sourcecode # or: pip install sourcecode
70
70
 
71
- ask version # ask 4.10.3 — and, on a build that has aged,
71
+ ask version # ask 4.10.5 — and, on a build that has aged,
72
72
  # how many releases have probably shipped since
73
73
  ```
74
74
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "4.10.3"
7
+ version = "4.10.5"
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"
@@ -30,7 +30,7 @@ dependencies = [
30
30
  "pathspec>=1.0",
31
31
  "ruamel.yaml>=0.18",
32
32
  "tomli>=2.0; python_version < '3.11'",
33
- "mcp>=1.0.0",
33
+ "mcp>=1.2,<2",
34
34
  "defusedxml>=0.7",
35
35
  ]
36
36
 
@@ -43,7 +43,7 @@ dev = [
43
43
  "pytest>=8",
44
44
  "ruff>=0.15",
45
45
  "mypy>=1.10",
46
- "mcp>=1.0.0",
46
+ "mcp>=1.2,<2",
47
47
  ]
48
48
  ast = [
49
49
  "tree-sitter>=0.21",
@@ -51,7 +51,7 @@ ast = [
51
51
  "tree-sitter-javascript>=0.21",
52
52
  ]
53
53
  mcp = [
54
- "mcp>=1.0.0",
54
+ "mcp>=1.2,<2",
55
55
  ]
56
56
 
57
57
  [[tool.mypy.overrides]]
@@ -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__ = "4.10.3"
7
+ __version__ = "4.10.5"
@@ -33,7 +33,7 @@ import json
33
33
  import subprocess
34
34
  from datetime import datetime, timezone
35
35
  from pathlib import Path
36
- from typing import TYPE_CHECKING
36
+ from typing import TYPE_CHECKING, Any
37
37
 
38
38
  from sourcecode import __version__ as TOOL_VERSION
39
39
  from sourcecode.architectural_delta import ArchMetrics, diff_metrics, extract_metrics
@@ -107,6 +107,35 @@ def _hotspots(fan_in: dict[str, int], top_n: int) -> list[dict]:
107
107
  return [{"symbol": s, "fan_in": c} for s, c in ranked[:top_n]]
108
108
 
109
109
 
110
+ def _fan_in_to_list(fan_in: dict[str, int]) -> list[dict]:
111
+ """Serialise the full fan-in map as an array of ``{symbol, fan_in}`` objects,
112
+ sorted by symbol — never a JSON object keyed by symbol.
113
+
114
+ C3-62: PowerShell's `ConvertFrom-Json` folds object keys case-insensitively, so
115
+ two distinct Java symbols differing only by case (legal — Java is case-sensitive)
116
+ collapse into one key and abort parsing the *whole* baseline document. `hotspots`
117
+ already used this array shape (`_hotspots`); the full map stored beside it did
118
+ not. An array of objects has no such ceiling on any consumer."""
119
+ return [{"symbol": s, "fan_in": c} for s, c in sorted(fan_in.items())]
120
+
121
+
122
+ def _fan_in_from_stored(value: Any) -> dict[str, int]:
123
+ """Read a stored ``fan_in`` field back into a symbol → count map.
124
+
125
+ Accepts both the array-of-objects shape (C3-62, current) and the legacy
126
+ object-keyed-by-symbol shape (baselines captured before this fix) so an older
127
+ baseline already committed to disk keeps diffing and trending correctly."""
128
+ if isinstance(value, list):
129
+ return {
130
+ str(item["symbol"]): int(item.get("fan_in", 0))
131
+ for item in value
132
+ if isinstance(item, dict) and "symbol" in item
133
+ }
134
+ if isinstance(value, dict):
135
+ return {str(k): int(v) for k, v in value.items()}
136
+ return {}
137
+
138
+
110
139
  def build_baseline(
111
140
  cir: "CanonicalRepositoryIR",
112
141
  *,
@@ -126,7 +155,6 @@ def build_baseline(
126
155
  that share them."""
127
156
  m = extract_metrics(cir)
128
157
  endpoints = sorted([meth, path] for (meth, path) in m.endpoints)
129
- fan_in = dict(sorted(m.fan_in.items()))
130
158
  cycles = sorted((sorted(c) for c in m.cycles), key=lambda mm: (len(mm), mm))
131
159
  return {
132
160
  "schema": ARCH_BASELINE_SCHEMA,
@@ -145,7 +173,7 @@ def build_baseline(
145
173
  },
146
174
  "hotspots": _hotspots(m.fan_in, hotspot_n),
147
175
  "endpoint_surface": endpoints,
148
- "fan_in": fan_in,
176
+ "fan_in": _fan_in_to_list(m.fan_in),
149
177
  "import_cycles": cycles,
150
178
  "env": env if env is not None else collect_env(),
151
179
  "provenance": (
@@ -209,7 +237,7 @@ def baseline_to_metrics(baseline: dict) -> ArchMetrics:
209
237
  endpoint_count=int(totals.get("endpoints", 0)),
210
238
  dependency_edge_count=int(totals.get("dependency_edges", 0)),
211
239
  endpoints=endpoints,
212
- fan_in={str(k): int(v) for k, v in (baseline.get("fan_in") or {}).items()},
240
+ fan_in=_fan_in_from_stored(baseline.get("fan_in")),
213
241
  cycles=frozenset(
214
242
  frozenset(str(m) for m in members)
215
243
  for members in baseline.get("import_cycles", [])
@@ -397,9 +425,10 @@ def build_trend(baselines: list[dict]) -> dict:
397
425
  if isinstance(h, dict) and h.get("symbol"):
398
426
  tracked.add(str(h["symbol"]))
399
427
 
428
+ fan_in_by_baseline = [_fan_in_from_stored(b.get("fan_in")) for b in baselines]
400
429
  hotspot_trends: list[dict] = []
401
430
  for sym in sorted(tracked):
402
- series = [int((b.get("fan_in") or {}).get(sym, 0)) for b in baselines]
431
+ series = [fmap.get(sym, 0) for fmap in fan_in_by_baseline]
403
432
  first, last = series[0], series[-1]
404
433
  hotspot_trends.append({
405
434
  "symbol": sym,
@@ -302,8 +302,48 @@ def render_markdown() -> str:
302
302
  return "\n".join(out)
303
303
 
304
304
 
305
+ def render_summary() -> str:
306
+ """The model as `ask cache model` prints it by default (C3-63).
307
+
308
+ One line per layer and one line per command — every command named (nothing
309
+ curated out, so the same completeness the battery checks against `render_text`
310
+ holds here too), but without the per-command prose (`note`) and timing
311
+ (`measured`) detail that made the default ~2 500 tokens of prose in a CLI whose
312
+ own selling argument is token economy. That detail is still one flag away.
313
+ """
314
+ lines: list[str] = []
315
+ lines.append(
316
+ "Invalidation: every layer keys on the exact tree state — any change to the "
317
+ "analysed files invalidates it, committed or not."
318
+ )
319
+ lines.append("")
320
+ lines.append("Layers")
321
+ id_width = max(len(lyr.id) for lyr in LAYERS)
322
+ for lyr in LAYERS:
323
+ lines.append(f" {lyr.id.ljust(id_width)} {lyr.stores}")
324
+ lines.append("")
325
+ lines.append(f"What a warm gives each command ({REFERENCE_REPOSITORY})")
326
+ cmd_width = max(len(c.command) for c in COMMANDS)
327
+ warm_width = max(len(label) for label in _WARM_LABEL.values())
328
+ for cmd in COMMANDS:
329
+ repeat = "repeat cached" if cmd.repeat else "recomputes"
330
+ layers = ", ".join(cmd.reads) or "none"
331
+ lines.append(
332
+ f" {cmd.command.ljust(cmd_width)} {_WARM_LABEL[cmd.warm].ljust(warm_width)} "
333
+ f"({repeat}) [{layers}]"
334
+ )
335
+ lines.append("")
336
+ lines.append(
337
+ "Full detail (measured timings, per-command notes): ask cache model --full"
338
+ )
339
+ lines.append("Tables for docs: ask cache model --markdown · machine-readable: --json")
340
+ return "\n".join(lines)
341
+
342
+
305
343
  def render_text() -> str:
306
- """The model as `ask cache model` prints it."""
344
+ """The model in full: every layer and command with its measured timing and note.
345
+
346
+ `ask cache model --full`. See `render_summary` for the default."""
307
347
  lines: list[str] = []
308
348
  lines.append("Invalidation — one rule for every layer")
309
349
  lines.append(" Every layer keys on the exact tree state. Any change to the analysed files")
@@ -15,9 +15,10 @@ from typing import Any, NoReturn, Optional, cast
15
15
  import typer
16
16
 
17
17
  from sourcecode import __version__
18
- from sourcecode.error_schema import INVALID_INPUT_CODE, build_error_envelope
18
+ from sourcecode.error_schema import EXECUTION_FAILED_CODE, INVALID_INPUT_CODE, build_error_envelope
19
19
  from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
20
20
  from sourcecode.progress import Progress
21
+ from sourcecode.progress import output_emitted as _progress_output_emitted
21
22
  from sourcecode import perf
22
23
  from sourcecode.caller_metrics import (
23
24
  CALLER_METRIC_RECONCILIATION,
@@ -888,8 +889,27 @@ def _safe_write_file(path: "Path", content: str) -> None:
888
889
  path = Path(path)
889
890
  path.parent.mkdir(parents=True, exist_ok=True)
890
891
  tmp = path.with_name(f".{path.name}.tmp-{os.getpid()}-{time.time_ns()}")
891
- tmp.write_text(content, encoding="utf-8")
892
+ # C3-54 part 2: the rename is atomic, the bytes were not durable. A
893
+ # process torn down between `write_text` and the OS flushing its page
894
+ # cache leaves a file that exists at full length and holds nothing —
895
+ # the same total loss the false `done` line reported as success, one
896
+ # layer down. fsync the data before the rename, then the directory
897
+ # entry, so a run that reports an output file has one.
898
+ with open(tmp, "w", encoding="utf-8") as fh:
899
+ fh.write(content)
900
+ fh.flush()
901
+ os.fsync(fh.fileno())
892
902
  tmp.replace(path)
903
+ try:
904
+ dir_fd = os.open(str(path.parent), os.O_RDONLY)
905
+ try:
906
+ os.fsync(dir_fd)
907
+ finally:
908
+ os.close(dir_fd)
909
+ except (OSError, AttributeError):
910
+ # Directory fsync is POSIX-only; on Windows the rename is already
911
+ # ordered against the data write. Never fail a good run over it.
912
+ pass
893
913
  try:
894
914
  from sourcecode.runs import active_run
895
915
  run = active_run()
@@ -1213,6 +1233,9 @@ def _emit_command_output(
1213
1233
  # The payload is on stdout here, so this cannot join it; it is a
1214
1234
  # notice and takes the terminal gate rather than dirtying a capture.
1215
1235
  _notice("✓ copied to clipboard")
1236
+ # The answer has been handed over. Any `done (Xs)` a command deferred is now
1237
+ # a true claim and prints here — never before this line (C3-54).
1238
+ _progress_output_emitted()
1216
1239
 
1217
1240
 
1218
1241
  def _emit_text_output(text: str, copy: bool) -> None:
@@ -1228,6 +1251,7 @@ def _emit_text_output(text: str, copy: bool) -> None:
1228
1251
  typer.echo(text)
1229
1252
  if copy and _copy_to_clipboard(text):
1230
1253
  _notice("✓ copied to clipboard")
1254
+ _progress_output_emitted()
1231
1255
 
1232
1256
 
1233
1257
  class _TextAnswer:
@@ -1257,6 +1281,7 @@ class _TextAnswer:
1257
1281
  def done(self, copy: bool) -> None:
1258
1282
  if copy and self._lines and _copy_to_clipboard(self.text):
1259
1283
  _notice("✓ copied to clipboard")
1284
+ _progress_output_emitted()
1260
1285
 
1261
1286
 
1262
1287
  # H-06: Intercept Click-level UsageError (unknown options, bad args) and emit JSON.
@@ -4825,8 +4850,15 @@ def repo_ir_cmd(
4825
4850
  size_bytes = len(raw_bytes)
4826
4851
  if gzip_output:
4827
4852
  import gzip as _gzip
4853
+ # Same durability rule as `_safe_write_file`: the bytes are on disk
4854
+ # before anything says they are (C3-54).
4828
4855
  with _gzip.open(output_path, "wb") as _gz:
4829
4856
  _gz.write(raw_bytes)
4857
+ _gz.flush()
4858
+ try:
4859
+ os.fsync(_gz.fileno())
4860
+ except (OSError, AttributeError):
4861
+ pass
4830
4862
  compressed_kb = output_path.stat().st_size // 1024
4831
4863
  size_kb = size_bytes // 1024
4832
4864
  # C3-26: when the payload went to a file, this confirmation IS the
@@ -4837,7 +4869,9 @@ def repo_ir_cmd(
4837
4869
  f"IR written to {output_path} ({compressed_kb}KB gzip, {size_kb}KB uncompressed)"
4838
4870
  )
4839
4871
  else:
4840
- output_path.write_bytes(raw_bytes)
4872
+ # Was a bare `write_bytes`: neither atomic nor durable, and the only
4873
+ # `-o` write in the CLI that skipped the shared seam (C3-54).
4874
+ _safe_write_file(output_path, output)
4841
4875
  size_kb = size_bytes // 1024
4842
4876
  if summary_only:
4843
4877
  typer.echo(
@@ -4850,6 +4884,7 @@ def repo_ir_cmd(
4850
4884
  f"IR written to {output_path} "
4851
4885
  f"({size_kb}KB, {n_nodes} nodes, {n_edges} edges)"
4852
4886
  )
4887
+ _progress_output_emitted()
4853
4888
  else:
4854
4889
  if gzip_output:
4855
4890
  _emit_error_json(
@@ -7402,7 +7437,14 @@ def spring_audit_cmd(
7402
7437
  except Exception:
7403
7438
  pass
7404
7439
  finally:
7405
- _prog.finish()
7440
+ # C3-54: `done` must not claim the answer exists before it does. The
7441
+ # field log showed `[ask] done (6.8m)` printed, then the process torn
7442
+ # down before `-o` was written — a false success on 6.8 minutes of
7443
+ # CPU. `stop()` here only clears the spinner (satisfies the standing
7444
+ # rule that a raised error must never print under a live spinner
7445
+ # frame); it prints no completion line. `_prog.finish()` runs below,
7446
+ # after the render AND the write have actually happened.
7447
+ _prog.stop()
7406
7448
  if table:
7407
7449
  output = _render_spring_audit_table(
7408
7450
  combined, rule_filter=rule_filter, top_n=top_n, min_severity=min_severity
@@ -7415,6 +7457,7 @@ def spring_audit_cmd(
7415
7457
  _total = combined.summary.get("total_findings", 0)
7416
7458
  _emit_command_output(output, output_path, copy,
7417
7459
  success_msg=f"Spring audit written to {output_path} ({_total} findings)")
7460
+ _prog.finish()
7418
7461
 
7419
7462
  if ci and combined.findings:
7420
7463
  raise typer.Exit(code=1)
@@ -9453,18 +9496,21 @@ def pr_impact_cmd(
9453
9496
  # --files accepts three shapes: a list file, '-' for stdin, or an inline
9454
9497
  # comma-separated list. The list-file shape was the only documented one and
9455
9498
  # the only one the error hint mentioned.
9456
- _files_raw = str(files).strip()
9499
+ # Both byte-carrying shapes decode through `text_input`: stdin and list files
9500
+ # arrive as somebody else's shell wrote them, and the locale codepage is not
9501
+ # evidence of their encoding (C3-58).
9502
+ from sourcecode import text_input as _text_input
9503
+
9504
+ _files_raw = str(files).replace(_text_input.BOM_CHAR, "").strip()
9457
9505
  if _files_raw == "-":
9458
- changed_files = [ln.strip() for ln in sys.stdin.read().splitlines() if ln.strip()]
9506
+ changed_files = _text_input.parse_path_lines(_text_input.read_stdin_text())
9459
9507
  _files_desc = "stdin"
9460
9508
  else:
9461
9509
  _files_path = Path(_files_raw)
9462
9510
  if _files_path.is_file():
9463
- changed_files = [
9464
- line.strip()
9465
- for line in _files_path.read_text(encoding="utf-8").splitlines()
9466
- if line.strip()
9467
- ]
9511
+ changed_files = _text_input.parse_path_lines(
9512
+ _text_input.read_text_file(_files_path)
9513
+ )
9468
9514
  _files_desc = str(_files_path)
9469
9515
  elif all(
9470
9516
  p.strip().lower().endswith(".java")
@@ -10301,6 +10347,9 @@ def modernize_cmd(
10301
10347
  sys.stdout.buffer.flush()
10302
10348
  except AttributeError:
10303
10349
  sys.stdout.write(output + "\n")
10350
+ # This branch predates `_emit_command_output`, so it also has to release the
10351
+ # deferred completion line itself (C3-54).
10352
+ _progress_output_emitted()
10304
10353
 
10305
10354
  if copy:
10306
10355
  _copy_to_clipboard(output)
@@ -11569,12 +11618,33 @@ def mcp_status(copy: bool = _copy_option()) -> None:
11569
11618
  _answer.say("")
11570
11619
 
11571
11620
  # Stage 1: Dependencies
11621
+ # C3-57: check the symbol this CLI actually consumes (FastMCP), not just
11622
+ # whether the root `mcp` package imports — `mcp>=2.0` imports cleanly but
11623
+ # has no `mcp.server.fastmcp` module, so importing only the root package
11624
+ # reports a green check for a build that will fail on `mcp serve`/`mcp list-tools`.
11625
+ def _installed_mcp_version() -> str:
11626
+ # The `mcp` package does not set `__version__`; read installed
11627
+ # distribution metadata instead, and fall back gracefully off a
11628
+ # normal install (editable installs, vendored copies, etc.).
11629
+ try:
11630
+ from importlib.metadata import version as _dist_version
11631
+ return _dist_version("mcp")
11632
+ except Exception:
11633
+ return "unknown"
11634
+
11572
11635
  try:
11573
- import mcp as _mcp_pkg # noqa: F401
11574
- _answer.say("Dependencies ✓ installed")
11636
+ from mcp.server.fastmcp import FastMCP # noqa: F401
11637
+ _mcp_ver = _installed_mcp_version()
11638
+ _answer.say(f"Dependencies ✓ installed (mcp {_mcp_ver})")
11575
11639
  except ImportError:
11576
- _answer.say("Dependencies ✗ missing")
11577
- _answer.say(" Fix: pip install sourcecode[mcp]")
11640
+ try:
11641
+ import mcp as _mcp_pkg # noqa: F401
11642
+ _mcp_ver = _installed_mcp_version()
11643
+ _answer.say(f"Dependencies ✗ mcp {_mcp_ver} incompatible (requires mcp>=1.2,<2)")
11644
+ _answer.say(" Fix: pip install 'sourcecode[mcp]' --upgrade")
11645
+ except ImportError:
11646
+ _answer.say("Dependencies ✗ missing")
11647
+ _answer.say(" Fix: pip install sourcecode[mcp]")
11578
11648
  _answer.say("")
11579
11649
 
11580
11650
  clients = detect_clients()
@@ -11789,9 +11859,32 @@ def mcp_list_tools(
11789
11859
  import asyncio
11790
11860
  import json as _json
11791
11861
 
11792
- from sourcecode.mcp.server import mcp as _mcp
11862
+ # C3-57: this command previously let a raw Python traceback through when
11863
+ # the installed `mcp` package was incompatible (e.g. mcp>=2.0, which has
11864
+ # no mcp.server.fastmcp) — the only command in the catalogue to do so.
11865
+ # Route failures through the same structured error contract as every
11866
+ # other command instead.
11867
+ try:
11868
+ from sourcecode.mcp.server import mcp as _mcp
11869
+ tools = asyncio.run(_mcp.list_tools())
11870
+ except ImportError as exc:
11871
+ _emit_error_json(
11872
+ EXECUTION_FAILED_CODE,
11873
+ f"MCP server dependencies are not importable: {exc}",
11874
+ hint="Run 'ask mcp status' to check the installed mcp package version — "
11875
+ "this CLI requires mcp>=1.2,<2. Fix: pip install 'sourcecode[mcp]' --upgrade",
11876
+ expected="mcp>=1.2,<2 installed with mcp.server.fastmcp.FastMCP importable.",
11877
+ )
11878
+ raise typer.Exit(code=1)
11879
+ except Exception as exc:
11880
+ _emit_error_json(
11881
+ EXECUTION_FAILED_CODE,
11882
+ f"Failed to list MCP tools: {type(exc).__name__}: {exc}",
11883
+ hint="Run 'ask mcp status' to check MCP server health.",
11884
+ expected="A list of registered MCP tools.",
11885
+ )
11886
+ raise typer.Exit(code=1)
11793
11887
 
11794
- tools = asyncio.run(_mcp.list_tools())
11795
11888
  tools_sorted = sorted(tools, key=lambda t: t.name)
11796
11889
 
11797
11890
  if json_output:
@@ -12057,6 +12150,11 @@ def cache_model_cmd(
12057
12150
  path: Path = typer.Argument(Path("."), help="Repository path for CLI consistency (model is global)."),
12058
12151
  json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
12059
12152
  markdown: bool = typer.Option(False, "--markdown", help="Output the tables published in the user guide."),
12153
+ full: bool = typer.Option(
12154
+ False, "--full",
12155
+ help="Full prose: every command's measured timing and note (~2.5k tokens). "
12156
+ "Default is a compact one-line-per-command summary.",
12157
+ ),
12060
12158
  copy: bool = _copy_option(),
12061
12159
  ) -> None:
12062
12160
  """What each cache layer stores, what invalidates it, and what a warm helps.
@@ -12070,6 +12168,10 @@ def cache_model_cmd(
12070
12168
 
12071
12169
  One rule covers invalidation: every layer keys on the exact tree state, so any
12072
12170
  change to the analysed files invalidates it, committed or not.
12171
+
12172
+ \b
12173
+ Default output is a compact summary (C3-63) — every command named, without the
12174
+ per-command measured timing and note. Pass --full for that detail.
12073
12175
  """
12074
12176
  from sourcecode import cache_model as _cmodel
12075
12177
 
@@ -12089,7 +12191,8 @@ def cache_model_cmd(
12089
12191
  f"Scope: {Path(path).resolve()} (model is global; path accepted for "
12090
12192
  "cache command grammar consistency)\n\n"
12091
12193
  )
12092
- _emit_text_output(_prefix + _cmodel.render_text(), copy)
12194
+ _body = _cmodel.render_text() if full else _cmodel.render_summary()
12195
+ _emit_text_output(_prefix + _body, copy)
12093
12196
 
12094
12197
 
12095
12198
  @cache_app.command("freshness", short_help="RIS freshness relative to the current git HEAD")