better-code-review-graph 3.28.0__tar.gz → 3.29.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.claude-plugin/plugin.json +1 -1
  2. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/CHANGELOG.md +17 -0
  3. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/PKG-INFO +1 -1
  4. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/pyproject.toml +1 -1
  5. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/server.json +2 -2
  6. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/cli.py +6 -3
  7. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/graph.md +1 -0
  8. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/summarizer.py +190 -9
  9. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/tools.py +5 -1
  10. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/conftest.py +14 -0
  11. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_cli_control_plane.py +34 -1
  12. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_summarize_graph_dispatch.py +11 -1
  13. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_summarizer.py +238 -0
  14. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.agents/skills/build-graph/SKILL.md +0 -0
  15. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.agents/skills/review-delta/SKILL.md +0 -0
  16. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.agents/skills/review-pr/SKILL.md +0 -0
  17. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.coderabbit.yaml +0 -0
  18. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.dockerignore +0 -0
  19. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/CODEOWNERS +0 -0
  20. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/review-learnings.md +0 -0
  24. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/rulesets/main.json +0 -0
  25. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/workflows/cd.yml +0 -0
  26. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.github/workflows/ci.yml +0 -0
  27. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.gitignore +0 -0
  28. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.infisical.json +0 -0
  29. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.jules/bolt.md +0 -0
  30. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.jules/sentinel.md +0 -0
  31. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/.pre-commit-config.yaml +0 -0
  32. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/AGENTS.md +0 -0
  33. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/BREAKING_CHANGES.md +0 -0
  34. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/CLAUDE.md +0 -0
  35. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/CODE_OF_CONDUCT.md +0 -0
  36. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/CONTRIBUTING.md +0 -0
  37. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/Dockerfile +0 -0
  38. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/LICENSE +0 -0
  39. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/LICENSE-MIT +0 -0
  40. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/NOTICE +0 -0
  41. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/README.md +0 -0
  42. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/SECURITY.md +0 -0
  43. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/alembic.ini +0 -0
  44. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/docs/HANDOVER.md +0 -0
  45. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/hooks/_graph_state.py +0 -0
  46. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/hooks/branch-sync-check.py +0 -0
  47. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/hooks/hooks.json +0 -0
  48. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/hooks/stale-graph-check.py +0 -0
  49. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/README +0 -0
  50. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/env.py +0 -0
  51. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/script.py.mako +0 -0
  52. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/001_baseline.py +0 -0
  53. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/002_summary_columns.py +0 -0
  54. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/003_federation.py +0 -0
  55. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/004_security_tags.py +0 -0
  56. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/005_temporal_columns.py +0 -0
  57. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/migrations/versions/006_commits_table.py +0 -0
  58. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/renovate.json +0 -0
  59. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/heuristic/cwe-22-path-traversal.yaml +0 -0
  60. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/heuristic/cwe-78-shell-command-sink.yaml +0 -0
  61. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/heuristic/cwe-89-sql-string-format.yaml +0 -0
  62. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/heuristic/cwe-95-eval-on-input.yaml +0 -0
  63. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/heuristic/hardcoded-secret.yaml +0 -0
  64. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/rules/semgrep/curated.yaml +0 -0
  65. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/scripts/benchmark_row_deserialization.py +0 -0
  66. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/scripts/enforce-commit.sh +0 -0
  67. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/scripts/preserve-diacritics.py +0 -0
  68. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/scripts/test_preserve_diacritics.py +0 -0
  69. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/semantic-release.toml +0 -0
  70. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/impact-audit/SKILL.md +0 -0
  71. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/onboard-repo/SKILL.md +0 -0
  72. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/refactor-check/SKILL.md +0 -0
  73. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/review-delta/SKILL.md +0 -0
  74. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/review-pr/SKILL.md +0 -0
  75. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/skills/security-sweep/SKILL.md +0 -0
  76. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/smithery.yaml +0 -0
  77. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/__init__.py +0 -0
  78. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/__main__.py +0 -0
  79. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/config.py +0 -0
  80. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/credential_state.py +0 -0
  81. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/__init__.py +0 -0
  82. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/config.md +0 -0
  83. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/query.md +0 -0
  84. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/recipes.md +0 -0
  85. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/review.md +0 -0
  86. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/docs/security.md +0 -0
  87. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/embeddings.py +0 -0
  88. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/exporter.py +0 -0
  89. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/federation.py +0 -0
  90. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/graph.py +0 -0
  91. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/importer.py +0 -0
  92. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/incremental.py +0 -0
  93. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/parser.py +0 -0
  94. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/py.typed +0 -0
  95. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/reranker.py +0 -0
  96. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/__init__.py +0 -0
  97. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/_types.py +0 -0
  98. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/fallback.py +0 -0
  99. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/go.py +0 -0
  100. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/java.py +0 -0
  101. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/python.py +0 -0
  102. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/rust.py +0 -0
  103. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/resolver/typescript.py +0 -0
  104. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/security/__init__.py +0 -0
  105. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/security/heuristic.py +0 -0
  106. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/security/semgrep_engine.py +0 -0
  107. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/server.py +0 -0
  108. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/temporal.py +0 -0
  109. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/src/better_code_review_graph/xpia.py +0 -0
  110. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/__init__.py +0 -0
  111. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/conftest_e2e.py +0 -0
  112. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/Sample.cs +0 -0
  113. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/SampleJava.java +0 -0
  114. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/callback_refs.js +0 -0
  115. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/caller_example.py +0 -0
  116. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/multi_call_example.py +0 -0
  117. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.c +0 -0
  118. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.cpp +0 -0
  119. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.kt +0 -0
  120. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.php +0 -0
  121. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.rb +0 -0
  122. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.sol +0 -0
  123. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample.swift +0 -0
  124. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample_go.go +0 -0
  125. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample_python.py +0 -0
  126. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample_rust.rs +0 -0
  127. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/sample_typescript.ts +0 -0
  128. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/fixtures/test_sample.py +0 -0
  129. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/integration/__init__.py +0 -0
  130. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/integration/test_multi_user_remote.py +0 -0
  131. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_alembic_baseline.py +0 -0
  132. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_as_of_diff.py +0 -0
  133. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_auto_resolve_ambiguity.py +0 -0
  134. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_bare_suffix_correctness.py +0 -0
  135. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_callers_not_found_reason.py +0 -0
  136. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_callers_of_coverage.py +0 -0
  137. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_cli.py +0 -0
  138. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_cli_contracts.py +0 -0
  139. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_cohere_dimensions.py +0 -0
  140. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_commits_migration.py +0 -0
  141. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_coverage_gaps.py +0 -0
  142. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_credential_state.py +0 -0
  143. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_credential_state_dehosted.py +0 -0
  144. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_crg_protocol_harness.py +0 -0
  145. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_custom_local_model.py +0 -0
  146. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_dynamic_dispatch_hints.py +0 -0
  147. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_e2e.py +0 -0
  148. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_embedder_retry_classification.py +0 -0
  149. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_embedding_migration.py +0 -0
  150. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_embeddings.py +0 -0
  151. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_embeddings_extra.py +0 -0
  152. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_export_graph_dispatch.py +0 -0
  153. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_export_graph_dispatch_path_traversal.py +0 -0
  154. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_exporter.py +0 -0
  155. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_federation.py +0 -0
  156. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_federation_migration.py +0 -0
  157. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_federation_smoke.py +0 -0
  158. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_full_live.py +0 -0
  159. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_g6_ux_status_accuracy.py +0 -0
  160. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_get_docs_section_error_path.py +0 -0
  161. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_get_review_context_no_diff.py +0 -0
  162. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_graph.py +0 -0
  163. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_graph_batch_fetch.py +0 -0
  164. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_graph_extra.py +0 -0
  165. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_graph_get_edges_by_targets_coverage.py +0 -0
  166. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_graph_stats_error_coverage.py +0 -0
  167. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_handle_not_found_coverage.py +0 -0
  168. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_help_recipes.py +0 -0
  169. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_heuristic.py +0 -0
  170. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_home_isolation.py +0 -0
  171. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_http_mcp_e2e.py +0 -0
  172. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_hull_cells.py +0 -0
  173. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_impact_payload_truncation.py +0 -0
  174. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_implements_edges.py +0 -0
  175. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_import_graph_dispatch.py +0 -0
  176. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_importer.py +0 -0
  177. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_incremental.py +0 -0
  178. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_incremental_extra.py +0 -0
  179. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_kind_filter_optimization.py +0 -0
  180. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_kinds_error_path.py +0 -0
  181. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_list_graph_stats_coverage.py +0 -0
  182. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_list_kinds_coverage.py +0 -0
  183. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_live_mcp.py +0 -0
  184. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_local_reranker.py +0 -0
  185. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_lookup_node_directly.py +0 -0
  186. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_multi_user.py +0 -0
  187. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_multilang.py +0 -0
  188. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_no_stale_package_name.py +0 -0
  189. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_oci_publication_sunset.py +0 -0
  190. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_oserror_coverage.py +0 -0
  191. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_parser.py +0 -0
  192. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_parser_extra.py +0 -0
  193. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_parser_grammar_failure.py +0 -0
  194. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_parser_source_text.py +0 -0
  195. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_path_resolution_fallback.py +0 -0
  196. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_payload_estimation.py +0 -0
  197. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_per_sub_graph_db.py +0 -0
  198. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_performance_n1.py +0 -0
  199. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_performance_n1_embeddings.py +0 -0
  200. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_phase_3_smoke.py +0 -0
  201. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_plugin_assets.py +0 -0
  202. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_pre_2_0_backup.py +0 -0
  203. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_renamed_in_diff.py +0 -0
  204. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_renamed_in_diff_exception.py +0 -0
  205. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_repo_filter.py +0 -0
  206. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_repo_filter_batching.py +0 -0
  207. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_repo_id_persistence.py +0 -0
  208. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolve_search_candidates.py +0 -0
  209. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_dispatcher.py +0 -0
  210. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_fallback.py +0 -0
  211. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_go.py +0 -0
  212. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_java.py +0 -0
  213. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_python.py +0 -0
  214. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_rust.py +0 -0
  215. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_resolver_typescript.py +0 -0
  216. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_response_header.py +0 -0
  217. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_response_payload_honesty.py +0 -0
  218. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_review_delta_line_shifts.py +0 -0
  219. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_review_languages_filter.py +0 -0
  220. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_reviewer_summary.py +0 -0
  221. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_search_edges_by_target_names.py +0 -0
  222. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_search_keyword_warning.py +0 -0
  223. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_security_injection.py +0 -0
  224. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_security_path_traversal.py +0 -0
  225. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_security_scan_batching.py +0 -0
  226. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_security_tags_migration.py +0 -0
  227. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_security_tool.py +0 -0
  228. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_semgrep_engine.py +0 -0
  229. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_server.py +0 -0
  230. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_server_help_coverage.py +0 -0
  231. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_serverinfo_version.py +0 -0
  232. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_setup_tool.py +0 -0
  233. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_shares_state_edges.py +0 -0
  234. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_spot_check.py +0 -0
  235. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_sql_security.py +0 -0
  236. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_stdio_direct.py +0 -0
  237. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_summary_schema.py +0 -0
  238. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_temporal.py +0 -0
  239. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_temporal_migration.py +0 -0
  240. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_tested_by_edges.py +0 -0
  241. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_tools.py +0 -0
  242. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_tools_build_error_coverage.py +0 -0
  243. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_tools_full.py +0 -0
  244. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_tools_get_docs_section_coverage.py +0 -0
  245. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_typescript_coverage_gaps.py +0 -0
  246. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_v17_coverage_gaps.py +0 -0
  247. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_xpia.py +0 -0
  248. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/tests/test_xpia_adversarial.py +0 -0
  249. {better_code_review_graph-3.28.0 → better_code_review_graph-3.29.0}/uv.lock +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "better-code-review-graph",
3
3
  "description": "Knowledge graph for token-efficient code reviews -- semantic search and call-graph resolution across your codebase.",
4
- "version": "3.28.0",
4
+ "version": "3.29.0",
5
5
  "userConfig": {
6
6
  "EMBEDDING_MODELS": {
7
7
  "type": "string",
@@ -2,6 +2,23 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v3.29.0 (2026-09-27)
6
+
7
+ ### Features
8
+
9
+ - Rank summarize queue with deterministic order and jev advisory batches
10
+ ([`2739dd8`](https://github.com/n24q02m/crg/commit/2739dd85386bb6080b97380902e021bc84bd4fba))
11
+
12
+
13
+ ## v3.28.1 (2026-09-27)
14
+
15
+ ### Bug Fixes
16
+
17
+ - Enforce no-auth loopback guard against config-sourced host
18
+ ([#1102](https://github.com/n24q02m/crg/pull/1102),
19
+ [`bf4afc3`](https://github.com/n24q02m/crg/commit/bf4afc338aa7443959fc522cfa980ef583d7e26d))
20
+
21
+
5
22
  ## v3.28.0 (2026-09-27)
6
23
 
7
24
  ### Code Style
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: better-code-review-graph
3
- Version: 3.28.0
3
+ Version: 3.29.0
4
4
  Summary: Token-efficient code review knowledge graph: semantic search and call-graph resolution.
5
5
  Author: n24q02m
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "better-code-review-graph"
7
- version = "3.28.0"
7
+ version = "3.29.0"
8
8
  description = "Token-efficient code review knowledge graph: semantic search and call-graph resolution."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/n24q02m/crg.git",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.28.0",
9
+ "version": "3.29.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "pypi",
13
13
  "identifier": "better-code-review-graph",
14
- "version": "3.28.0",
14
+ "version": "3.29.0",
15
15
  "runtimeHint": "uvx",
16
16
  "runtimeArgs": [
17
17
  "--python",
@@ -537,18 +537,21 @@ def _handle_server_start(args: argparse.Namespace) -> int:
537
537
  from .server import serve_main
538
538
 
539
539
  os.environ["MCP_TRANSPORT"] = "http"
540
- # run_http() resolves bind address from these (falls back to config).
541
- host = args.host or os.environ.get("MCP_HOST")
540
+ # run_http() bind precedence: MCP_HOST env > settings.server.host; --port
541
+ # hands off via MCP_PORT (honored at server.py:986-989).
542
+ settings = load_instance_settings()
542
543
  if args.host:
543
544
  os.environ["MCP_HOST"] = args.host
544
545
  if args.port:
545
546
  os.environ["MCP_PORT"] = str(args.port)
546
547
  # Security property carried over from the wp2-wip line: an
547
548
  # unauthenticated (no-auth) listener must never bind off-loopback.
549
+ # Resolve the EFFECTIVE bind host the way run_http does.
550
+ host = args.host or os.environ.get("MCP_HOST") or settings.server.host
548
551
  if (
549
552
  host
550
553
  and host not in {"localhost", "127.0.0.1", "::1"}
551
- and load_instance_settings().server.auth == "no-auth"
554
+ and settings.server.auth == "no-auth"
552
555
  ):
553
556
  print(
554
557
  "crg refuses to start: auth = 'no-auth' only permits loopback "
@@ -148,6 +148,7 @@ graph is unchanged. A provider key alone does not enable summaries.
148
148
  **Cost cap + caching:**
149
149
  - Default cap is 500 LLM calls per invocation. Tune with `max_nodes` for tighter budgets.
150
150
  - Repeat invocations skip nodes whose `source_hash` + full selected model match. `summary_provider` stores the `provider/model` identity, so switching models within one provider invalidates stale summaries too.
151
+ - The work queue has a deterministic base order (`ORDER BY id`). When the host configures a `[models.jev_score]` cell, the pending queue is additionally re-prioritized by one advisory jev call per ~50-node batch (never one call per node); the result carries a `jev_ranking` receipt (`used`, `batches`, `ranked_nodes`, `failopen_reason`). Any jev failure is fail-open: the queue keeps its base order and only the receipt notes it.
151
152
 
152
153
  **Example:**
153
154
  ```json
@@ -8,10 +8,15 @@ fallback. The full selected model is persisted in ``summary_provider`` so
8
8
  switching models invalidates the cached summary.
9
9
 
10
10
  Dispatch goes through hull-core's ``OpenAICompatClient`` (async httpx, one
11
- client per batch). The batch queue keeps its exact historical shape: a
12
- single ``SELECT ... LIMIT ?`` over Function nodes with no ORDER BY (spec
13
- §7 K4 — the queue only lines work up; it does not reorder or change the
14
- schema), capped at ``max_nodes`` per run.
11
+ client per batch). The batch queue is a single ``SELECT ... LIMIT ?`` over
12
+ Function nodes with a deterministic base ordering (``ORDER BY id``, spec
13
+ §7 K4). On top of that base order, an optional jev ranking layer re-orders
14
+ the pending (cache-miss) queue in ~50-node batches — exactly one advisory
15
+ ``jev_score`` call per batch, never one call per node — and is strictly
16
+ fail-open: any jev unavailability (cell not configured, call error,
17
+ unparseable reply) leaves the queue in base order and is recorded in the
18
+ run receipt only. The queue never changes the schema, only the processing
19
+ order.
15
20
  """
16
21
 
17
22
  from __future__ import annotations
@@ -19,6 +24,7 @@ from __future__ import annotations
19
24
  import asyncio
20
25
  import hashlib
21
26
  import logging
27
+ import re
22
28
  from dataclasses import dataclass
23
29
  from typing import Any
24
30
 
@@ -81,6 +87,22 @@ def summary_cell() -> Any:
81
87
  return cell if cell.configured else None
82
88
 
83
89
 
90
+ def jev_score_cell() -> Any:
91
+ """The configured jev_score cell (host-only key), or ``None``.
92
+
93
+ Unlike :func:`summary_cell`, resolution failures also yield ``None``:
94
+ the ranking layer is advisory (spec §7 K4 fail-open), so an unreadable
95
+ config may only ever disable prioritization, never the queue itself.
96
+ """
97
+ try:
98
+ from .config import resolve_cells
99
+
100
+ cell = resolve_cells().get("jev_score")
101
+ except Exception: # config problems must only disable ranking
102
+ return None
103
+ return cell if cell is not None and cell.configured else None
104
+
105
+
84
106
  # ---------------------------------------------------------------------------
85
107
  # Single-node LLM summarization
86
108
  # ---------------------------------------------------------------------------
@@ -144,6 +166,31 @@ def _auth_mode() -> str:
144
166
  # Default cap on per-run LLM calls. Override per-call via the max_nodes parameter.
145
167
  DEFAULT_MAX_NODES_PER_RUN = 500
146
168
 
169
+ # jev ranking layer (spec §7 K4): the pending queue is re-ordered in batches
170
+ # of this many nodes, with exactly one advisory jev_score call per batch.
171
+ JEV_BATCH_SIZE = 50
172
+
173
+ # Per-node excerpt handed to the ranking call, and the cap on the recorded
174
+ # fail-open reason so a chatty provider error cannot bloat the receipt.
175
+ _JEV_EXCERPT_CHARS = 160
176
+ _FAILOPEN_REASON_MAX_CHARS = 200
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class JevRankingReceipt:
181
+ """Provenance of the jev ranking layer for one batch_summarize run.
182
+
183
+ ``used=True`` means jev re-ordered the queue. ``used=False`` with a
184
+ ``None`` ``failopen_reason`` means the layer was disabled (no jev cell,
185
+ or a single-node queue); a non-``None`` reason means jev was attempted,
186
+ failed, and the queue fell back to base order.
187
+ """
188
+
189
+ used: bool
190
+ batches: int = 0 # ranked batches; one jev call each
191
+ ranked_nodes: int = 0 # nodes whose queue position jev decided
192
+ failopen_reason: str | None = None # why ranking was skipped, if attempted
193
+
147
194
 
148
195
  @dataclass(frozen=True)
149
196
  class BatchSummarizeResult:
@@ -154,6 +201,132 @@ class BatchSummarizeResult:
154
201
  skipped_no_provider: bool = False # True iff no chat cell is configured
155
202
  provider: str | None = None # provider used (None if skipped)
156
203
  errors: int = 0 # nodes where the chat call raised; counted, batch continues
204
+ jev_ranking: JevRankingReceipt | None = None # queue ranking provenance
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # jev queue ranking (spec §7 K4 — advisory, fail-open)
209
+ # ---------------------------------------------------------------------------
210
+
211
+ _JEV_RANK_PROMPT_PREFIX = (
212
+ "You are prioritizing a code-review summarization queue. Rank the "
213
+ "numbered functions below by value to a code reviewer: core logic, "
214
+ "complex control flow and public entry points first; trivial accessors, "
215
+ "constants and one-line wrappers last.\n"
216
+ "Reply with ONLY a JSON array of the indices in priority order, e.g. "
217
+ "[3, 1, 0, 2]. Every index must appear exactly once.\n\nFunctions:\n"
218
+ )
219
+
220
+ # Scan the reply for numeric tokens (mnemo-wp4-writer jev precedent:
221
+ # ``[+-]?\\d*\\.\\d+|\\d+``); a reply without any number raises instead of
222
+ # guessing an order.
223
+ _NUMBER_RE = re.compile(r"[+-]?\d*\.\d+|\d+")
224
+
225
+
226
+ def _parse_jev_ranking(content: str, batch_size: int) -> list[int]:
227
+ """Parse jev's priority order out of its reply.
228
+
229
+ Follows the mnemo-wp4-writer precedent: scan for numeric tokens, raise
230
+ when the reply carries no numbers at all. Indices outside
231
+ ``range(batch_size)`` and duplicates are ignored; an incomplete
232
+ permutation raises so the caller can fail open to base order.
233
+ """
234
+ tokens = _NUMBER_RE.findall(content)
235
+ if not tokens:
236
+ raise ValueError(f"jev ranking reply has no numeric tokens: {content[:80]!r}")
237
+ order: list[int] = []
238
+ seen: set[int] = set()
239
+ for token in tokens:
240
+ index = int(float(token))
241
+ if 0 <= index < batch_size and index not in seen:
242
+ seen.add(index)
243
+ order.append(index)
244
+ if len(order) != batch_size:
245
+ raise ValueError(
246
+ f"jev ranking incomplete: got {len(order)} of {batch_size} indices"
247
+ )
248
+ return order
249
+
250
+
251
+ async def _jev_rank_batch(
252
+ client: Any,
253
+ batch: list[tuple[int, NodeNeedingSummary]],
254
+ ) -> list[tuple[int, NodeNeedingSummary]]:
255
+ """One advisory jev call: return ``batch`` re-ordered by review priority."""
256
+ listing = _JEV_RANK_PROMPT_PREFIX
257
+ for position, (_, node) in enumerate(batch):
258
+ condensed = " ".join(node.source_text.split())[:_JEV_EXCERPT_CHARS]
259
+ listing += (
260
+ f"{position}. ({len(node.source_text.splitlines())} lines) {condensed}\n"
261
+ )
262
+ content = await client.chat(
263
+ [{"role": "user", "content": listing}],
264
+ max_tokens=1024,
265
+ reasoning={"exclude": True},
266
+ )
267
+ order = _parse_jev_ranking(content, len(batch))
268
+ return [batch[index] for index in order]
269
+
270
+
271
+ async def _jev_rank_queue(
272
+ client: Any,
273
+ pending: list[tuple[int, NodeNeedingSummary]],
274
+ batch_size: int = JEV_BATCH_SIZE,
275
+ ) -> tuple[list[tuple[int, NodeNeedingSummary]], int]:
276
+ """Rank the whole pending queue, one jev call per ``batch_size`` chunk.
277
+
278
+ Any failure propagates: the caller discards partial rankings and keeps
279
+ the queue in base order (fail-open is all-or-nothing per run).
280
+ """
281
+ ordered: list[tuple[int, NodeNeedingSummary]] = []
282
+ batches = 0
283
+ for start in range(0, len(pending), batch_size):
284
+ batch = pending[start : start + batch_size]
285
+ ordered.extend(await _jev_rank_batch(client, batch))
286
+ batches += 1
287
+ return ordered, batches
288
+
289
+
290
+ def _maybe_jev_rank(
291
+ pending: list[tuple[int, NodeNeedingSummary]],
292
+ ) -> tuple[JevRankingReceipt, list[tuple[int, NodeNeedingSummary]]]:
293
+ """Re-order ``pending`` via the advisory jev ranking layer (spec §7 K4).
294
+
295
+ Fail-open: an unconfigured cell returns the queue unchanged silently;
296
+ any ranking failure logs a warning and returns the queue unchanged with
297
+ the reason on the receipt. Only a fully successful pass re-orders.
298
+ """
299
+ if len(pending) < 2: # nothing to prioritize
300
+ return JevRankingReceipt(used=False), pending
301
+
302
+ jev_cell = jev_score_cell()
303
+ if jev_cell is None:
304
+ return JevRankingReceipt(used=False), pending
305
+
306
+ async def _rank() -> tuple[list[tuple[int, NodeNeedingSummary]], int]:
307
+ client = OpenAICompatClient(jev_cell, auth_mode=_auth_mode())
308
+ try:
309
+ return await _jev_rank_queue(client, pending, JEV_BATCH_SIZE)
310
+ finally:
311
+ await client.aclose()
312
+
313
+ try:
314
+ ordered, batches = asyncio.run(_rank())
315
+ except Exception as exc: # ranking is advisory only — never block the queue
316
+ logger.warning("jev ranking failed; queue keeps base order: %s", exc)
317
+ return (
318
+ JevRankingReceipt(
319
+ used=False,
320
+ batches=0,
321
+ ranked_nodes=0,
322
+ failopen_reason=str(exc)[:_FAILOPEN_REASON_MAX_CHARS],
323
+ ),
324
+ pending,
325
+ )
326
+ return (
327
+ JevRankingReceipt(used=True, batches=batches, ranked_nodes=len(ordered)),
328
+ ordered,
329
+ )
157
330
 
158
331
 
159
332
  def batch_summarize(
@@ -165,8 +338,11 @@ def batch_summarize(
165
338
 
166
339
  Iteration scope: at most ``max_nodes`` Function-kind nodes whose
167
340
  ``source_text`` is non-null, selected by one ``SELECT ... LIMIT ?``
168
- with no ORDER BY (queue order is the storage engine's row order —
169
- spec §7 K4 keeps this exactly). For each candidate:
341
+ with deterministic base ordering (``ORDER BY id`` — spec §7 K4). When
342
+ the host configures a jev_score cell, the pending (cache-miss) queue is
343
+ additionally re-ordered by one advisory jev call per ~50-node batch;
344
+ any jev failure leaves the queue in base order (fail-open) and is
345
+ recorded in :attr:`BatchSummarizeResult.jev_ranking`. For each candidate:
170
346
 
171
347
  - If stored summary + ``summary_provider`` + ``source_hash`` all match
172
348
  the selected model + freshly-computed source hash, it's a cache hit
@@ -204,7 +380,8 @@ def batch_summarize(
204
380
  # because it copies large `source_text` columns.
205
381
  cursor = store._conn.execute(
206
382
  "SELECT id, source_text, source_hash, summary, summary_provider FROM nodes "
207
- "WHERE kind='Function' AND source_text IS NOT NULL LIMIT ?",
383
+ "WHERE kind='Function' AND source_text IS NOT NULL "
384
+ "ORDER BY id LIMIT ?",
208
385
  (max_nodes,),
209
386
  )
210
387
 
@@ -240,6 +417,8 @@ def batch_summarize(
240
417
  )
241
418
  )
242
419
 
420
+ ranking, pending = _maybe_jev_rank(pending)
421
+
243
422
  if pending:
244
423
  generated, errors = asyncio.run(
245
424
  _summarize_pending(store, cell, cache_provider, pending)
@@ -253,6 +432,7 @@ def batch_summarize(
253
432
  skipped_no_provider=False,
254
433
  provider=cache_provider,
255
434
  errors=errors,
435
+ jev_ranking=ranking,
256
436
  )
257
437
 
258
438
 
@@ -264,8 +444,9 @@ async def _summarize_pending(
264
444
  ) -> tuple[int, int]:
265
445
  """Run the pending queue through one shared hull client.
266
446
 
267
- Sequential, cursor order preserved. Each failure is logged + counted;
268
- the batch continues (fail-open per-node, spec §7).
447
+ Sequential, queue order preserved (base ``ORDER BY id`` order,
448
+ optionally re-ranked by the jev layer). Each failure is logged and
449
+ counted; the batch continues (fail-open per-node, spec §7).
269
450
  """
270
451
  client = OpenAICompatClient(cell, auth_mode=_auth_mode())
271
452
  generated = 0
@@ -16,6 +16,7 @@ import hashlib
16
16
  import json
17
17
  import logging
18
18
  import time
19
+ from dataclasses import asdict
19
20
  from pathlib import Path
20
21
  from typing import Any
21
22
 
@@ -2933,7 +2934,9 @@ def summarize_graph_dispatch(
2933
2934
  max_nodes: Max LLM calls per invocation. Default 500.
2934
2935
 
2935
2936
  Returns:
2936
- Dict with status, generated, cached, errors, provider, summary.
2937
+ Dict with status, generated, cached, errors, provider, jev_ranking,
2938
+ summary. ``jev_ranking`` carries the queue-ranking receipt (None on
2939
+ the skipped path); see ``summarizer.JevRankingReceipt``.
2937
2940
  """
2938
2941
  from . import summarizer
2939
2942
 
@@ -2962,6 +2965,7 @@ def summarize_graph_dispatch(
2962
2965
  "generated": result.generated,
2963
2966
  "cached": result.cached,
2964
2967
  "errors": result.errors,
2968
+ "jev_ranking": asdict(result.jev_ranking) if result.jev_ranking else None,
2965
2969
  "summary": (
2966
2970
  f"Summarized {result.generated} new + {result.cached} cached Function "
2967
2971
  f"node(s) via {result.provider}"
@@ -14,6 +14,20 @@ import pytest
14
14
  REAL_HOME = Path.home()
15
15
 
16
16
 
17
+ @pytest.fixture(autouse=True)
18
+ def _jev_ranking_disabled_by_default(monkeypatch):
19
+ """Default every test to jev-ranking-off (spec §7 K4).
20
+
21
+ Ranking is an advisory layer driven by the host's ``[models.jev_score]``
22
+ cell; without this opt-out a developer/CI box with that cell configured
23
+ would leak live jev calls into unrelated queue tests. Tests exercising
24
+ the ranking layer re-patch ``summarizer.jev_score_cell`` themselves.
25
+ """
26
+ monkeypatch.setattr(
27
+ "better_code_review_graph.summarizer.jev_score_cell", lambda: None
28
+ )
29
+
30
+
17
31
  def pytest_addoption(parser):
18
32
  """Add --setup and --browser CLI options for E2E tests."""
19
33
  parser.addoption("--setup", choices=["relay", "env", "plugin"], default="env")
@@ -73,7 +73,13 @@ def test_db_path_prints_graph_db(capsys) -> None:
73
73
  assert out.endswith("graph.db")
74
74
 
75
75
 
76
- def test_server_start_forces_http_transport(capsys, monkeypatch) -> None:
76
+ def test_server_start_forces_http_transport(capsys, tmp_path, monkeypatch) -> None:
77
+ cfg = tmp_path / "cfg"
78
+ cfg.mkdir()
79
+ (cfg / "config.toml").write_text(
80
+ '[server]\nhost = "127.0.0.1"\nport = 61000\nauth = "no-auth"\n'
81
+ )
82
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(cfg))
77
83
  monkeypatch.delenv("MCP_TRANSPORT", raising=False)
78
84
  monkeypatch.delenv("MCP_HOST", raising=False)
79
85
  monkeypatch.delenv("MCP_PORT", raising=False)
@@ -110,6 +116,7 @@ def test_server_start_refuses_no_auth_off_loopback(
110
116
  )
111
117
  monkeypatch.setenv("CRG_CONFIG_DIR", str(cfg))
112
118
  monkeypatch.delenv("MCP_TRANSPORT", raising=False)
119
+ monkeypatch.delenv("MCP_HOST", raising=False)
113
120
  serve_called = False
114
121
 
115
122
  def must_not_serve() -> None:
@@ -124,6 +131,32 @@ def test_server_start_refuses_no_auth_off_loopback(
124
131
  assert serve_called is False
125
132
 
126
133
 
134
+ def test_server_start_refuses_no_auth_config_host_off_loopback(
135
+ capsys, tmp_path, monkeypatch
136
+ ) -> None:
137
+ """Bypass regression: no --host/MCP_HOST, config-sourced host binds."""
138
+ cfg = tmp_path / "cfg"
139
+ cfg.mkdir()
140
+ (cfg / "config.toml").write_text(
141
+ '[server]\nhost = "0.0.0.0"\nport = 61000\nauth = "no-auth"\n'
142
+ )
143
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(cfg))
144
+ monkeypatch.delenv("MCP_TRANSPORT", raising=False)
145
+ monkeypatch.delenv("MCP_HOST", raising=False)
146
+ serve_called = False
147
+
148
+ def must_not_serve() -> None:
149
+ nonlocal serve_called # noqa: F841
150
+ serve_called = True # pragma: no cover
151
+
152
+ monkeypatch.setattr("better_code_review_graph.server.serve_main", must_not_serve)
153
+ rc = _run(capsys, "server", "start")
154
+ err = capsys.readouterr().err
155
+ assert rc == 2
156
+ assert "no-auth" in err and "loopback" in err
157
+ assert serve_called is False
158
+
159
+
127
160
  def test_unknown_subcommand_is_rc2(capsys) -> None:
128
161
  rc = _run(capsys, "nope")
129
162
  err = capsys.readouterr().err
@@ -5,7 +5,10 @@ from __future__ import annotations
5
5
  from unittest.mock import patch
6
6
 
7
7
  from better_code_review_graph.graph import GraphStore
8
- from better_code_review_graph.summarizer import BatchSummarizeResult
8
+ from better_code_review_graph.summarizer import (
9
+ BatchSummarizeResult,
10
+ JevRankingReceipt,
11
+ )
9
12
  from better_code_review_graph.tools import summarize_graph_dispatch
10
13
 
11
14
 
@@ -40,6 +43,7 @@ def test_dispatch_returns_ok_with_counts_on_success(tmp_path, monkeypatch):
40
43
  skipped_no_provider=False,
41
44
  provider="gemini",
42
45
  errors=0,
46
+ jev_ranking=JevRankingReceipt(used=False),
43
47
  )
44
48
 
45
49
  with patch("better_code_review_graph.tools._get_store") as mock_get_store:
@@ -59,6 +63,12 @@ def test_dispatch_returns_ok_with_counts_on_success(tmp_path, monkeypatch):
59
63
  assert result["generated"] == 3
60
64
  assert result["cached"] == 1
61
65
  assert result["errors"] == 0
66
+ assert result["jev_ranking"] == {
67
+ "used": False,
68
+ "batches": 0,
69
+ "ranked_nodes": 0,
70
+ "failopen_reason": None,
71
+ }
62
72
  assert "3 new" in result["summary"]
63
73
  assert "1 cached" in result["summary"]
64
74
  assert "gemini" in result["summary"]