better-code-review-graph 3.27.2__tar.gz → 3.28.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.27.2 → better_code_review_graph-3.28.0}/.claude-plugin/plugin.json +1 -1
  2. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/CHANGELOG.md +34 -0
  3. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/PKG-INFO +2 -2
  4. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/pyproject.toml +2 -2
  5. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/server.json +2 -2
  6. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/cli.py +150 -1
  7. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/server.py +5 -1
  8. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/integration/test_multi_user_remote.py +2 -4
  9. better_code_review_graph-3.28.0/tests/test_cli_control_plane.py +131 -0
  10. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_cohere_dimensions.py +2 -7
  11. better_code_review_graph-3.28.0/tests/test_credential_state.py +164 -0
  12. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_embeddings.py +7 -5
  13. better_code_review_graph-3.28.0/tests/test_home_isolation.py +108 -0
  14. better_code_review_graph-3.28.0/tests/test_http_mcp_e2e.py +201 -0
  15. better_code_review_graph-3.28.0/tests/test_hull_cells.py +84 -0
  16. better_code_review_graph-3.28.0/tests/test_multi_user.py +111 -0
  17. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_summarizer.py +1 -5
  18. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/uv.lock +2 -2
  19. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.agents/skills/build-graph/SKILL.md +0 -0
  20. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.agents/skills/review-delta/SKILL.md +0 -0
  21. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.agents/skills/review-pr/SKILL.md +0 -0
  22. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.coderabbit.yaml +0 -0
  23. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.dockerignore +0 -0
  24. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/CODEOWNERS +0 -0
  25. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  26. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  27. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  28. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/review-learnings.md +0 -0
  29. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/rulesets/main.json +0 -0
  30. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/workflows/cd.yml +0 -0
  31. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.github/workflows/ci.yml +0 -0
  32. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.gitignore +0 -0
  33. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.infisical.json +0 -0
  34. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.jules/bolt.md +0 -0
  35. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.jules/sentinel.md +0 -0
  36. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/.pre-commit-config.yaml +0 -0
  37. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/AGENTS.md +0 -0
  38. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/BREAKING_CHANGES.md +0 -0
  39. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/CLAUDE.md +0 -0
  40. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/CODE_OF_CONDUCT.md +0 -0
  41. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/CONTRIBUTING.md +0 -0
  42. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/Dockerfile +0 -0
  43. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/LICENSE +0 -0
  44. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/LICENSE-MIT +0 -0
  45. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/NOTICE +0 -0
  46. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/README.md +0 -0
  47. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/SECURITY.md +0 -0
  48. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/alembic.ini +0 -0
  49. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/docs/HANDOVER.md +0 -0
  50. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/hooks/_graph_state.py +0 -0
  51. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/hooks/branch-sync-check.py +0 -0
  52. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/hooks/hooks.json +0 -0
  53. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/hooks/stale-graph-check.py +0 -0
  54. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/README +0 -0
  55. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/env.py +0 -0
  56. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/script.py.mako +0 -0
  57. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/001_baseline.py +0 -0
  58. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/002_summary_columns.py +0 -0
  59. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/003_federation.py +0 -0
  60. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/004_security_tags.py +0 -0
  61. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/005_temporal_columns.py +0 -0
  62. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/migrations/versions/006_commits_table.py +0 -0
  63. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/renovate.json +0 -0
  64. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/heuristic/cwe-22-path-traversal.yaml +0 -0
  65. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/heuristic/cwe-78-shell-command-sink.yaml +0 -0
  66. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/heuristic/cwe-89-sql-string-format.yaml +0 -0
  67. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/heuristic/cwe-95-eval-on-input.yaml +0 -0
  68. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/heuristic/hardcoded-secret.yaml +0 -0
  69. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/rules/semgrep/curated.yaml +0 -0
  70. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/scripts/benchmark_row_deserialization.py +0 -0
  71. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/scripts/enforce-commit.sh +0 -0
  72. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/scripts/preserve-diacritics.py +0 -0
  73. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/scripts/test_preserve_diacritics.py +0 -0
  74. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/semantic-release.toml +0 -0
  75. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/impact-audit/SKILL.md +0 -0
  76. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/onboard-repo/SKILL.md +0 -0
  77. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/refactor-check/SKILL.md +0 -0
  78. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/review-delta/SKILL.md +0 -0
  79. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/review-pr/SKILL.md +0 -0
  80. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/skills/security-sweep/SKILL.md +0 -0
  81. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/smithery.yaml +0 -0
  82. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/__init__.py +0 -0
  83. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/__main__.py +0 -0
  84. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/config.py +0 -0
  85. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/credential_state.py +0 -0
  86. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/__init__.py +0 -0
  87. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/config.md +0 -0
  88. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/graph.md +0 -0
  89. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/query.md +0 -0
  90. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/recipes.md +0 -0
  91. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/review.md +0 -0
  92. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/docs/security.md +0 -0
  93. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/embeddings.py +0 -0
  94. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/exporter.py +0 -0
  95. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/federation.py +0 -0
  96. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/graph.py +0 -0
  97. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/importer.py +0 -0
  98. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/incremental.py +0 -0
  99. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/parser.py +0 -0
  100. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/py.typed +0 -0
  101. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/reranker.py +0 -0
  102. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/__init__.py +0 -0
  103. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/_types.py +0 -0
  104. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/fallback.py +0 -0
  105. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/go.py +0 -0
  106. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/java.py +0 -0
  107. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/python.py +0 -0
  108. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/rust.py +0 -0
  109. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/resolver/typescript.py +0 -0
  110. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/security/__init__.py +0 -0
  111. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/security/heuristic.py +0 -0
  112. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/security/semgrep_engine.py +0 -0
  113. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/summarizer.py +0 -0
  114. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/temporal.py +0 -0
  115. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/tools.py +0 -0
  116. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/src/better_code_review_graph/xpia.py +0 -0
  117. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/__init__.py +0 -0
  118. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/conftest.py +0 -0
  119. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/conftest_e2e.py +0 -0
  120. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/Sample.cs +0 -0
  121. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/SampleJava.java +0 -0
  122. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/callback_refs.js +0 -0
  123. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/caller_example.py +0 -0
  124. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/multi_call_example.py +0 -0
  125. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.c +0 -0
  126. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.cpp +0 -0
  127. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.kt +0 -0
  128. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.php +0 -0
  129. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.rb +0 -0
  130. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.sol +0 -0
  131. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample.swift +0 -0
  132. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample_go.go +0 -0
  133. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample_python.py +0 -0
  134. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample_rust.rs +0 -0
  135. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/sample_typescript.ts +0 -0
  136. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/fixtures/test_sample.py +0 -0
  137. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/integration/__init__.py +0 -0
  138. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_alembic_baseline.py +0 -0
  139. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_as_of_diff.py +0 -0
  140. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_auto_resolve_ambiguity.py +0 -0
  141. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_bare_suffix_correctness.py +0 -0
  142. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_callers_not_found_reason.py +0 -0
  143. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_callers_of_coverage.py +0 -0
  144. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_cli.py +0 -0
  145. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_cli_contracts.py +0 -0
  146. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_commits_migration.py +0 -0
  147. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_coverage_gaps.py +0 -0
  148. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_credential_state_dehosted.py +0 -0
  149. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_crg_protocol_harness.py +0 -0
  150. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_custom_local_model.py +0 -0
  151. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_dynamic_dispatch_hints.py +0 -0
  152. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_e2e.py +0 -0
  153. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_embedder_retry_classification.py +0 -0
  154. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_embedding_migration.py +0 -0
  155. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_embeddings_extra.py +0 -0
  156. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_export_graph_dispatch.py +0 -0
  157. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_export_graph_dispatch_path_traversal.py +0 -0
  158. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_exporter.py +0 -0
  159. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_federation.py +0 -0
  160. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_federation_migration.py +0 -0
  161. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_federation_smoke.py +0 -0
  162. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_full_live.py +0 -0
  163. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_g6_ux_status_accuracy.py +0 -0
  164. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_get_docs_section_error_path.py +0 -0
  165. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_get_review_context_no_diff.py +0 -0
  166. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_graph.py +0 -0
  167. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_graph_batch_fetch.py +0 -0
  168. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_graph_extra.py +0 -0
  169. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_graph_get_edges_by_targets_coverage.py +0 -0
  170. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_graph_stats_error_coverage.py +0 -0
  171. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_handle_not_found_coverage.py +0 -0
  172. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_help_recipes.py +0 -0
  173. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_heuristic.py +0 -0
  174. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_impact_payload_truncation.py +0 -0
  175. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_implements_edges.py +0 -0
  176. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_import_graph_dispatch.py +0 -0
  177. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_importer.py +0 -0
  178. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_incremental.py +0 -0
  179. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_incremental_extra.py +0 -0
  180. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_kind_filter_optimization.py +0 -0
  181. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_kinds_error_path.py +0 -0
  182. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_list_graph_stats_coverage.py +0 -0
  183. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_list_kinds_coverage.py +0 -0
  184. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_live_mcp.py +0 -0
  185. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_local_reranker.py +0 -0
  186. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_lookup_node_directly.py +0 -0
  187. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_multilang.py +0 -0
  188. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_no_stale_package_name.py +0 -0
  189. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_oci_publication_sunset.py +0 -0
  190. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_oserror_coverage.py +0 -0
  191. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_parser.py +0 -0
  192. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_parser_extra.py +0 -0
  193. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_parser_grammar_failure.py +0 -0
  194. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_parser_source_text.py +0 -0
  195. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_path_resolution_fallback.py +0 -0
  196. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_payload_estimation.py +0 -0
  197. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_per_sub_graph_db.py +0 -0
  198. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_performance_n1.py +0 -0
  199. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_performance_n1_embeddings.py +0 -0
  200. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_phase_3_smoke.py +0 -0
  201. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_plugin_assets.py +0 -0
  202. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_pre_2_0_backup.py +0 -0
  203. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_renamed_in_diff.py +0 -0
  204. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_renamed_in_diff_exception.py +0 -0
  205. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_repo_filter.py +0 -0
  206. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_repo_filter_batching.py +0 -0
  207. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_repo_id_persistence.py +0 -0
  208. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolve_search_candidates.py +0 -0
  209. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_dispatcher.py +0 -0
  210. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_fallback.py +0 -0
  211. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_go.py +0 -0
  212. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_java.py +0 -0
  213. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_python.py +0 -0
  214. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_rust.py +0 -0
  215. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_resolver_typescript.py +0 -0
  216. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_response_header.py +0 -0
  217. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_response_payload_honesty.py +0 -0
  218. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_review_delta_line_shifts.py +0 -0
  219. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_review_languages_filter.py +0 -0
  220. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_reviewer_summary.py +0 -0
  221. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_search_edges_by_target_names.py +0 -0
  222. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_search_keyword_warning.py +0 -0
  223. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_security_injection.py +0 -0
  224. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_security_path_traversal.py +0 -0
  225. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_security_scan_batching.py +0 -0
  226. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_security_tags_migration.py +0 -0
  227. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_security_tool.py +0 -0
  228. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_semgrep_engine.py +0 -0
  229. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_server.py +0 -0
  230. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_server_help_coverage.py +0 -0
  231. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_serverinfo_version.py +0 -0
  232. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_setup_tool.py +0 -0
  233. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_shares_state_edges.py +0 -0
  234. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_spot_check.py +0 -0
  235. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_sql_security.py +0 -0
  236. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_stdio_direct.py +0 -0
  237. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_summarize_graph_dispatch.py +0 -0
  238. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_summary_schema.py +0 -0
  239. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_temporal.py +0 -0
  240. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_temporal_migration.py +0 -0
  241. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_tested_by_edges.py +0 -0
  242. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_tools.py +0 -0
  243. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_tools_build_error_coverage.py +0 -0
  244. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_tools_full.py +0 -0
  245. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_tools_get_docs_section_coverage.py +0 -0
  246. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_typescript_coverage_gaps.py +0 -0
  247. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_v17_coverage_gaps.py +0 -0
  248. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_xpia.py +0 -0
  249. {better_code_review_graph-3.27.2 → better_code_review_graph-3.28.0}/tests/test_xpia_adversarial.py +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.27.2",
4
+ "version": "3.28.0",
5
5
  "userConfig": {
6
6
  "EMBEDDING_MODELS": {
7
7
  "type": "string",
@@ -2,6 +2,40 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v3.28.0 (2026-09-27)
6
+
7
+ ### Code Style
8
+
9
+ - Apply ruff format repo-wide (de-host format debt)
10
+ ([`de1a18f`](https://github.com/n24q02m/crg/commit/de1a18f0dcfa46bc53ce37e8f15898a927de16f6))
11
+
12
+ - Fix ruff F401/I001 left by residuals scrub
13
+ ([`194ce5f`](https://github.com/n24q02m/crg/commit/194ce5f2298b0be9d3e67435c5cbdaa5d23ee5c8))
14
+
15
+ ### Features
16
+
17
+ - Add host control-plane CLI (server start, token hash, config, db path)
18
+ ([#1101](https://github.com/n24q02m/crg/pull/1101),
19
+ [`ab10fb5`](https://github.com/n24q02m/crg/commit/ab10fb535c74b88d769d9ff94f654a127dd7cf36))
20
+
21
+ ### Testing
22
+
23
+ - **wp2**: Add token-auth HTTP MCP e2e + per-sub storage/auth coverage
24
+ ([#1100](https://github.com/n24q02m/crg/pull/1100),
25
+ [`11cf315`](https://github.com/n24q02m/crg/commit/11cf3159af5ef90e133091344e12ad7d95eb4091))
26
+
27
+
28
+ ## v3.27.3 (2026-09-26)
29
+
30
+ ### Bug Fixes
31
+
32
+ - **deps**: Lock file maintenance ([#1097](https://github.com/n24q02m/crg/pull/1097),
33
+ [`89ff192`](https://github.com/n24q02m/crg/commit/89ff1928bd90f8834456a59d184a6eb25e3c4936))
34
+
35
+ - **deps**: Update fastretrieval to >=1.11.1,<2 ([#1096](https://github.com/n24q02m/crg/pull/1096),
36
+ [`58b1559`](https://github.com/n24q02m/crg/commit/58b1559028cb323104c83b77e95560eb5c5c3c78))
37
+
38
+
5
39
  ## v3.27.2 (2026-09-26)
6
40
 
7
41
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: better-code-review-graph
3
- Version: 3.27.2
3
+ Version: 3.28.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
@@ -16,7 +16,7 @@ Requires-Python: ==3.13.*
16
16
  Requires-Dist: alembic<2,>=1.20.0
17
17
  Requires-Dist: defusedxml>=0.7.1
18
18
  Requires-Dist: fastmcp<4,>=3.4.7
19
- Requires-Dist: fastretrieval<2,>=1.8.2
19
+ Requires-Dist: fastretrieval<2,>=1.11.1
20
20
  Requires-Dist: httpx
21
21
  Requires-Dist: hull-core
22
22
  Requires-Dist: mcp<2,>=1.30.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "better-code-review-graph"
7
- version = "3.27.2"
7
+ version = "3.28.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"
@@ -24,7 +24,7 @@ dependencies = [
24
24
  "tree-sitter-language-pack>=1.20.0,<2",
25
25
  "networkx>=3.7,<4",
26
26
  "watchdog>=6.0.0,<7",
27
- "fastretrieval>=1.8.2,<2",
27
+ "fastretrieval>=1.11.1,<2",
28
28
  "httpx",
29
29
  "pydantic-settings",
30
30
  # De-hosted shared core (WP1): token auth (3 modes), per-task model cells
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/n24q02m/crg.git",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.27.2",
9
+ "version": "3.28.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "pypi",
13
13
  "identifier": "better-code-review-graph",
14
- "version": "3.27.2",
14
+ "version": "3.28.0",
15
15
  "runtimeHint": "uvx",
16
16
  "runtimeArgs": [
17
17
  "--python",
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import argparse
11
11
  import json
12
+ import os
12
13
  from importlib.metadata import PackageNotFoundError
13
14
  from importlib.metadata import version as pkg_version
14
15
  from typing import Any
@@ -513,6 +514,148 @@ def _handle_security(args: argparse.Namespace) -> int:
513
514
  return _print_result(result)
514
515
 
515
516
 
517
+ # ---------------------------------------------------------------------------
518
+ # Host control plane (hull-core pattern): server/token/config/db
519
+ # ---------------------------------------------------------------------------
520
+
521
+
522
+ def _configure_server(sub: argparse.ArgumentParser) -> None:
523
+ server_sub = sub.add_subparsers(dest="server_command", required=True)
524
+ start = server_sub.add_parser(
525
+ "start", help="start the HTTP MCP server (blocking; Ctrl+C stops)"
526
+ )
527
+ start.add_argument("--host", default=None, help="bind host (default from config)")
528
+ start.add_argument(
529
+ "--port", type=int, default=None, help="bind port (default from config)"
530
+ )
531
+
532
+
533
+ def _handle_server_start(args: argparse.Namespace) -> int:
534
+ import sys
535
+
536
+ from .config import load_instance_settings
537
+ from .server import serve_main
538
+
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")
542
+ if args.host:
543
+ os.environ["MCP_HOST"] = args.host
544
+ if args.port:
545
+ os.environ["MCP_PORT"] = str(args.port)
546
+ # Security property carried over from the wp2-wip line: an
547
+ # unauthenticated (no-auth) listener must never bind off-loopback.
548
+ if (
549
+ host
550
+ and host not in {"localhost", "127.0.0.1", "::1"}
551
+ and load_instance_settings().server.auth == "no-auth"
552
+ ):
553
+ print(
554
+ "crg refuses to start: auth = 'no-auth' only permits loopback "
555
+ "binds; set [server] auth to 'token' or 'multi' for a shared "
556
+ "listener",
557
+ file=sys.stderr,
558
+ )
559
+ return 2
560
+ try:
561
+ serve_main()
562
+ except KeyboardInterrupt:
563
+ pass
564
+ except SystemExit as exc: # e.g. no-auth non-loopback bind refusal
565
+ if str(exc):
566
+ print(f"error: {exc}", file=sys.stderr)
567
+ return 2
568
+ return 0
569
+
570
+
571
+ def _configure_token(sub: argparse.ArgumentParser) -> None:
572
+ token_sub = sub.add_subparsers(dest="token_command", required=True)
573
+ token_hash_p = token_sub.add_parser(
574
+ "hash", help="mint a scrypt token_hash from a plaintext token"
575
+ )
576
+ token_hash_p.add_argument(
577
+ "token", help="plaintext token (keep it out of shell history)"
578
+ )
579
+
580
+
581
+ def _handle_token_hash(args: argparse.Namespace) -> int:
582
+ from hull_core.auth.tokens import hash_token
583
+
584
+ print(hash_token(args.token))
585
+ return 0
586
+
587
+
588
+ def _configure_config(sub: argparse.ArgumentParser) -> None:
589
+ config_sub = sub.add_subparsers(dest="config_command", required=True)
590
+ init_p = config_sub.add_parser(
591
+ "init", help="write the instance config.toml from the template"
592
+ )
593
+ init_p.add_argument(
594
+ "--force", action="store_true", help="overwrite an existing config"
595
+ )
596
+ config_sub.add_parser("path", help="print the config file path")
597
+ config_sub.add_parser(
598
+ "show", help="print the effective config (template if absent)"
599
+ )
600
+
601
+
602
+ def _handle_config_init(args: argparse.Namespace) -> int:
603
+ import sys
604
+
605
+ from hull_core.config.settings import write_default_config
606
+
607
+ from .config import crg_config_dir
608
+
609
+ try:
610
+ path = write_default_config(config_dir=crg_config_dir(), force=args.force)
611
+ except FileExistsError as exc:
612
+ print(f"error: {exc}", file=sys.stderr)
613
+ return 2
614
+ print(f"wrote {path}")
615
+ return 0
616
+
617
+
618
+ def _handle_config_path(_args: argparse.Namespace) -> int:
619
+ from .config import crg_config_dir
620
+
621
+ print(crg_config_dir() / "config.toml")
622
+ return 0
623
+
624
+
625
+ def _handle_config_show(_args: argparse.Namespace) -> int:
626
+
627
+ from hull_core.config.settings import CONFIG_TEMPLATE
628
+
629
+ from .config import crg_config_dir
630
+
631
+ path = crg_config_dir() / "config.toml"
632
+ if path.is_file():
633
+ print(path.read_text(encoding="utf-8"), end="")
634
+ else:
635
+ print(CONFIG_TEMPLATE, end="")
636
+ return 0
637
+
638
+
639
+ def _configure_db(sub: argparse.ArgumentParser) -> None:
640
+ db_sub = sub.add_subparsers(dest="db_command", required=True)
641
+ db_sub.add_parser("path", help="print the repo-local graph.db path")
642
+
643
+
644
+ def _handle_db_path(_args: argparse.Namespace) -> int:
645
+ from .incremental import find_project_root, get_db_path
646
+
647
+ print(get_db_path(find_project_root(None)))
648
+ return 0
649
+
650
+
651
+ def _handle_config(args: argparse.Namespace) -> int:
652
+ if args.config_command == "path":
653
+ return _handle_config_path(args)
654
+ if args.config_command == "show":
655
+ return _handle_config_show(args)
656
+ return _handle_config_init(args)
657
+
658
+
516
659
  # ---------------------------------------------------------------------------
517
660
  # Main entrypoint
518
661
  # ---------------------------------------------------------------------------
@@ -533,6 +676,10 @@ def main() -> int:
533
676
  "query": (_configure_query, _handle_query),
534
677
  "review": (_configure_review, _handle_review),
535
678
  "security": (_configure_security, _handle_security),
679
+ "server": (_configure_server, _handle_server_start),
680
+ "token": (_configure_token, _handle_token_hash),
681
+ "config": (_configure_config, _handle_config),
682
+ "db": (_configure_db, _handle_db_path),
536
683
  }
537
684
 
538
685
  argv = sys.argv[1:]
@@ -546,7 +693,9 @@ def main() -> int:
546
693
  if argv[0] in ("-h", "--help"):
547
694
  names = ", ".join(sorted(handlers))
548
695
  print("usage: better-code-review-graph [-h] [--version] [<subcommand> ...]")
549
- print("Any other flags/args are passed through to the MCP server (e.g. --http).")
696
+ print(
697
+ "Any other flags/args are passed through to the MCP server (e.g. --http)."
698
+ )
550
699
  print(f"subcommands: {names}")
551
700
  return 0
552
701
  if argv[0] in ("--version", "-V"):
@@ -990,7 +990,11 @@ async def run_http(port: int = 0) -> None:
990
990
  if host is None:
991
991
  host = settings.server.host
992
992
 
993
- users = load_users(settings.server.users_file) if settings.server.auth == "multi" else None
993
+ users = (
994
+ load_users(settings.server.users_file)
995
+ if settings.server.auth == "multi"
996
+ else None
997
+ )
994
998
  authenticator = Authenticator(settings, users=users, limiter=SlidingWindowLimiter())
995
999
  app = mcp.http_app(
996
1000
  path="/mcp",
@@ -58,12 +58,10 @@ def test_sub_stores_do_not_see_each_others_nodes(tmp_path, monkeypatch):
58
58
  _build(store_b, "bob")
59
59
 
60
60
  names_a = {
61
- row["name"]
62
- for row in store_a._conn.execute("SELECT name FROM nodes")
61
+ row["name"] for row in store_a._conn.execute("SELECT name FROM nodes")
63
62
  }
64
63
  names_b = {
65
- row["name"]
66
- for row in store_b._conn.execute("SELECT name FROM nodes")
64
+ row["name"] for row in store_b._conn.execute("SELECT name FROM nodes")
67
65
  }
68
66
  assert names_a == {"alice_fn"}
69
67
  assert names_b == {"bob_fn"}
@@ -0,0 +1,131 @@
1
+ """Host control-plane CLI: server/token/config/db subcommands (WP2 lifecycle).
2
+
3
+ Fast, hermetic coverage of the operator surface — the server path is tested
4
+ end-to-end (spawn + bearer + build/query) in tests/test_http_mcp_e2e.py; here
5
+ we pin the dispatch contract and the env handoff to run_http().
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ from better_code_review_graph.cli import main
14
+
15
+
16
+ def _run(capsys, *argv: str) -> int:
17
+ sys.argv = ["better-code-review-graph", *argv]
18
+ return main()
19
+
20
+
21
+ def test_token_hash_matches_hull_primitive(capsys) -> None:
22
+ from hull_core.auth.tokens import verify_token
23
+
24
+ rc = _run(capsys, "token", "hash", "s3cret-token")
25
+ out = capsys.readouterr().out.strip()
26
+ assert rc == 0
27
+ # scrypt hashes are salted per call: verify rather than compare.
28
+ assert verify_token("s3cret-token", out)
29
+ assert not verify_token("wrong-token", out)
30
+ rc2 = _run(capsys, "token", "hash", "s3cret-token")
31
+ out2 = capsys.readouterr().out.strip()
32
+ assert rc2 == 0
33
+ assert out2 != out # fresh salt per mint
34
+
35
+
36
+ def test_config_path_prints_instance_config(capsys, tmp_path, monkeypatch) -> None:
37
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path))
38
+ rc = _run(capsys, "config", "path")
39
+ out = capsys.readouterr().out.strip()
40
+ assert rc == 0
41
+ assert out == str(Path(tmp_path) / "config.toml")
42
+
43
+
44
+ def test_config_init_writes_then_refuses_without_force(
45
+ capsys, tmp_path, monkeypatch
46
+ ) -> None:
47
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path))
48
+ rc = _run(capsys, "config", "init")
49
+ assert rc == 0
50
+ assert (Path(tmp_path) / "config.toml").is_file()
51
+
52
+ rc = _run(capsys, "config", "init")
53
+ err = capsys.readouterr().err
54
+ assert rc == 2
55
+ assert "error" in err
56
+
57
+ rc = _run(capsys, "config", "init", "--force")
58
+ assert rc == 0
59
+
60
+
61
+ def test_config_show_prints_template_when_absent(capsys, tmp_path, monkeypatch) -> None:
62
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path / "missing"))
63
+ rc = _run(capsys, "config", "show")
64
+ out = capsys.readouterr().out
65
+ assert rc == 0
66
+ assert "[server]" in out # template content
67
+
68
+
69
+ def test_db_path_prints_graph_db(capsys) -> None:
70
+ rc = _run(capsys, "db", "path")
71
+ out = capsys.readouterr().out.strip()
72
+ assert rc == 0
73
+ assert out.endswith("graph.db")
74
+
75
+
76
+ def test_server_start_forces_http_transport(capsys, monkeypatch) -> None:
77
+ monkeypatch.delenv("MCP_TRANSPORT", raising=False)
78
+ monkeypatch.delenv("MCP_HOST", raising=False)
79
+ monkeypatch.delenv("MCP_PORT", raising=False)
80
+ seen: dict[str, object] = {}
81
+
82
+ def fake_serve_main() -> None:
83
+ seen["transport"] = __import__("os").environ.get("MCP_TRANSPORT")
84
+ seen["host"] = __import__("os").environ.get("MCP_HOST")
85
+ seen["port"] = __import__("os").environ.get("MCP_PORT")
86
+
87
+ monkeypatch.setattr("better_code_review_graph.server.serve_main", fake_serve_main)
88
+
89
+ rc = _run(capsys, "server", "start", "--host", "127.0.0.1", "--port", "60077")
90
+ assert rc == 0
91
+ assert seen == {"transport": "http", "host": "127.0.0.1", "port": "60077"}
92
+
93
+
94
+ def test_server_start_keyboard_interrupt_is_clean(capsys, monkeypatch) -> None:
95
+ def interrupted() -> None:
96
+ raise KeyboardInterrupt
97
+
98
+ monkeypatch.setattr("better_code_review_graph.server.serve_main", interrupted)
99
+ rc = _run(capsys, "server", "start")
100
+ assert rc == 0
101
+
102
+
103
+ def test_server_start_refuses_no_auth_off_loopback(
104
+ capsys, tmp_path, monkeypatch
105
+ ) -> None:
106
+ cfg = tmp_path / "cfg"
107
+ cfg.mkdir()
108
+ (cfg / "config.toml").write_text(
109
+ '[server]\nhost = "127.0.0.1"\nport = 61000\nauth = "no-auth"\n'
110
+ )
111
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(cfg))
112
+ monkeypatch.delenv("MCP_TRANSPORT", raising=False)
113
+ serve_called = False
114
+
115
+ def must_not_serve() -> None:
116
+ nonlocal serve_called # noqa: F841
117
+ serve_called = True # pragma: no cover
118
+
119
+ monkeypatch.setattr("better_code_review_graph.server.serve_main", must_not_serve)
120
+ rc = _run(capsys, "server", "start", "--host", "0.0.0.0")
121
+ err = capsys.readouterr().err
122
+ assert rc == 2
123
+ assert "no-auth" in err and "loopback" in err
124
+ assert serve_called is False
125
+
126
+
127
+ def test_unknown_subcommand_is_rc2(capsys) -> None:
128
+ rc = _run(capsys, "nope")
129
+ err = capsys.readouterr().err
130
+ assert rc == 2
131
+ assert "unknown subcommand" in err
@@ -10,7 +10,6 @@ are unchanged.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- import os
14
13
  import sqlite3
15
14
  from unittest.mock import patch
16
15
 
@@ -44,9 +43,7 @@ def _dispatch(*vectors):
44
43
  def test_unsupported_cohere_width_is_rejected_without_dispatch():
45
44
  # The width-selection guard lives inside _post_embeddings and must fire
46
45
  # BEFORE any client is constructed / request is spent.
47
- with patch(
48
- "hull_core.providers.openai_spec.OpenAICompatClient"
49
- ) as client_cls:
46
+ with patch("hull_core.providers.openai_spec.OpenAICompatClient") as client_cls:
50
47
  with pytest.raises(ValueError, match="requires dimensions"):
51
48
  _post_embeddings(
52
49
  ["hello"],
@@ -152,9 +149,7 @@ def test_cohere_reopen_reembed_legacy_width_and_query(tmp_path):
152
149
  assert [
153
150
  call.kwargs["input_type"] for call in dispatch.call_args_list
154
151
  ] == ["search_document", "search_query"]
155
- assert all(
156
- call.args[2] == 1024 for call in dispatch.call_args_list
157
- )
152
+ assert all(call.args[2] == 1024 for call in dispatch.call_args_list)
158
153
  finally:
159
154
  store.close()
160
155
  with sqlite3.connect(db) as conn:
@@ -0,0 +1,164 @@
1
+ """Tests for credential_state -- hull-core identity scoping + cell state.
2
+
3
+ WP2 de-host: there is no credential store, no relay, and no per-user key
4
+ bucket. What remains is (a) the host-level CONFIGURED/LOCAL cell state,
5
+ (b) the per-request subject contextvar with hull ``multi``-mode fallback,
6
+ and (c) the ``subs/<namespace>/graph.db`` path helpers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+
13
+ import better_code_review_graph.credential_state as cs
14
+ from better_code_review_graph.credential_state import (
15
+ SERVER_NAME,
16
+ CredentialState,
17
+ get_current_sub,
18
+ get_state,
19
+ resolve_credential_state,
20
+ set_current_sub,
21
+ set_state,
22
+ )
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Helpers
26
+ # ---------------------------------------------------------------------------
27
+
28
+
29
+ @pytest.fixture(autouse=True)
30
+ def _reset_module_state():
31
+ """Reset module-level state before and after each test."""
32
+ set_current_sub(None)
33
+ set_state(CredentialState.LOCAL)
34
+ yield
35
+ set_current_sub(None)
36
+ set_state(CredentialState.LOCAL)
37
+
38
+
39
+ def _write_config(tmp_path, body: str):
40
+ tmp_path.mkdir(parents=True, exist_ok=True)
41
+ (tmp_path / "config.toml").write_text(body, encoding="utf-8")
42
+ return tmp_path
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Constants
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ class TestConstants:
51
+ def test_server_name(self):
52
+ assert SERVER_NAME == "better-code-review-graph"
53
+
54
+ def test_credential_state_enum_values(self):
55
+ assert CredentialState.CONFIGURED.value == "configured"
56
+ assert CredentialState.LOCAL.value == "local"
57
+
58
+ def test_enum_has_exactly_two_states(self):
59
+ # No relay-era states may reappear (awaiting_setup / setup_in_progress).
60
+ assert {member.name for member in CredentialState} == {"CONFIGURED", "LOCAL"}
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # get_state / set_state
65
+ # ---------------------------------------------------------------------------
66
+
67
+
68
+ class TestStateAccessors:
69
+ def test_set_state_changes_state(self):
70
+ set_state(CredentialState.CONFIGURED)
71
+ assert get_state() == CredentialState.CONFIGURED
72
+
73
+ def test_set_state_to_local(self):
74
+ set_state(CredentialState.LOCAL)
75
+ assert get_state() == CredentialState.LOCAL
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # resolve_credential_state (host model cells)
80
+ # ---------------------------------------------------------------------------
81
+
82
+
83
+ class TestResolveCredentialState:
84
+ def test_no_config_is_local(self, tmp_path, monkeypatch):
85
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path / "absent"))
86
+ assert resolve_credential_state() == CredentialState.LOCAL
87
+ assert get_state() == CredentialState.LOCAL
88
+
89
+ def test_chat_cell_key_sets_configured(self, tmp_path, monkeypatch):
90
+ _write_config(
91
+ tmp_path,
92
+ '[models.chat]\nbase_url = "https://openrouter.ai/api/v1"\napi_key = "sk-h"\nmodel = "m"\n',
93
+ )
94
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path))
95
+ assert resolve_credential_state() == CredentialState.CONFIGURED
96
+
97
+ def test_embed_cell_env_key_sets_configured(self, tmp_path, monkeypatch):
98
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path))
99
+ monkeypatch.setenv("HULL_EMBED_API_KEY", "sk-e")
100
+ try:
101
+ assert resolve_credential_state() == CredentialState.CONFIGURED
102
+ finally:
103
+ monkeypatch.delenv("HULL_EMBED_API_KEY")
104
+
105
+ def test_cell_without_key_stays_local(self, tmp_path, monkeypatch):
106
+ _write_config(
107
+ tmp_path,
108
+ '[models.embed]\nbase_url = "https://openrouter.ai/api/v1"\napi_key = ""\nmodel = "voyage-4-lite"\n',
109
+ )
110
+ monkeypatch.setenv("CRG_CONFIG_DIR", str(tmp_path))
111
+ assert resolve_credential_state() == CredentialState.LOCAL
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Credential isolation regression guard
116
+ # ---------------------------------------------------------------------------
117
+
118
+
119
+ class TestCredentialIsolation:
120
+ """better-code-review-graph must not write to peer MCP servers' configs."""
121
+
122
+ def test_no_peer_key_sharing_helper(self):
123
+ assert not hasattr(cs, "_share_cloud_keys_to_peers")
124
+
125
+
126
+ class TestCutSurfaceAbsent:
127
+ """The relay/BYOK surface must not reappear after the de-host."""
128
+
129
+ @pytest.mark.parametrize(
130
+ "symbol",
131
+ [
132
+ "CLOUD_KEYS",
133
+ "credentials_for_current_request",
134
+ "config_value_for_current_request",
135
+ "save_credentials",
136
+ "store_for_sub",
137
+ "read_for_sub",
138
+ "get_setup_url",
139
+ "reset_state",
140
+ "load_credentials",
141
+ "clear_credentials",
142
+ ],
143
+ )
144
+ def test_symbol_removed(self, symbol):
145
+ assert not hasattr(cs, symbol)
146
+
147
+
148
+ # ---------------------------------------------------------------------------
149
+ # Current subject
150
+ # ---------------------------------------------------------------------------
151
+
152
+
153
+ class TestCurrentSub:
154
+ def test_default_is_none(self):
155
+ assert get_current_sub() is None
156
+
157
+ def test_set_current_sub_roundtrip(self):
158
+ set_current_sub("user-a")
159
+ assert get_current_sub() == "user-a"
160
+
161
+ def test_clearing_restores_none(self):
162
+ set_current_sub("user-a")
163
+ set_current_sub(None)
164
+ assert get_current_sub() is None
@@ -167,7 +167,9 @@ class TestResolveBackend:
167
167
  """
168
168
  import logging
169
169
 
170
- with caplog.at_level(logging.WARNING, logger="better_code_review_graph.embeddings"):
170
+ with caplog.at_level(
171
+ logging.WARNING, logger="better_code_review_graph.embeddings"
172
+ ):
171
173
  with patch.dict(os.environ, {"EMBEDDING_BACKEND": "cloud"}, clear=True):
172
174
  assert resolve_backend() == "local"
173
175
  with patch.dict(os.environ, {"EMBEDDING_BACKEND": "litellm"}, clear=True):
@@ -340,7 +342,9 @@ class TestResolveEmbeddingChain:
340
342
  "CO_API_KEY",
341
343
  ):
342
344
  monkeypatch.delenv(k, raising=False)
343
- with caplog.at_level(logging.WARNING, logger="better_code_review_graph.embeddings"):
345
+ with caplog.at_level(
346
+ logging.WARNING, logger="better_code_review_graph.embeddings"
347
+ ):
344
348
  for legacy in ("cloud", "litellm", "local"):
345
349
  monkeypatch.setenv("EMBEDDING_BACKEND", legacy)
346
350
  assert resolve_backend() == "local"
@@ -610,9 +614,7 @@ class TestCloudEmbeddingBackend:
610
614
  with _patched_client(vectors=[]) as client_cls:
611
615
  with pytest.raises(ValueError, match="vector count"):
612
616
  backend.embed_texts(["a"])
613
- assert client_cls.instances[-1].calls == [
614
- {"texts": ["a"], "dimensions": None}
615
- ]
617
+ assert client_cls.instances[-1].calls == [{"texts": ["a"], "dimensions": None}]
616
618
 
617
619
  def test_width_mismatch_fails_closed(self):
618
620
  """Provider width != requested dimensions must fail loudly."""