better-code-review-graph 3.2.0__tar.gz → 3.3.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 (88) hide show
  1. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.claude-plugin/plugin.json +1 -1
  2. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/workflows/cd.yml +10 -5
  3. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/workflows/ci.yml +23 -9
  4. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.gitignore +4 -0
  5. better_code_review_graph-3.3.0/.jules/sentinel.md +4 -0
  6. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.pre-commit-config.yaml +3 -3
  7. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/Dockerfile +2 -2
  8. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/PKG-INFO +12 -12
  9. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/gemini-extension.json +1 -1
  10. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/pyproject.toml +16 -16
  11. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/server.json +2 -2
  12. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/incremental.py +14 -4
  13. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/parser.py +87 -53
  14. better_code_review_graph-3.3.0/src/better_code_review_graph/relay_schema.py +47 -0
  15. better_code_review_graph-3.3.0/src/better_code_review_graph/relay_setup.py +125 -0
  16. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/tools.py +6 -1
  17. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/conftest.py +10 -0
  18. better_code_review_graph-3.3.0/tests/test_coverage_gaps.py +695 -0
  19. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_graph_extra.py +2 -0
  20. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_relay.py +21 -35
  21. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_tools_full.py +39 -14
  22. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/uv.lock +69 -69
  23. better_code_review_graph-3.2.0/src/better_code_review_graph/relay_schema.py +0 -18
  24. better_code_review_graph-3.2.0/src/better_code_review_graph/relay_setup.py +0 -88
  25. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/build-graph/SKILL.md +0 -0
  26. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/review-delta/SKILL.md +0 -0
  27. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/review-pr/SKILL.md +0 -0
  28. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.dockerignore +0 -0
  29. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/CODEOWNERS +0 -0
  30. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  32. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  33. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/best_practices.md +0 -0
  34. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/rulesets/main.json +0 -0
  35. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.infisical.json +0 -0
  36. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/AGENTS.md +0 -0
  37. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CHANGELOG.md +0 -0
  38. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CLAUDE.md +0 -0
  39. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CODE_OF_CONDUCT.md +0 -0
  40. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CONTRIBUTING.md +0 -0
  41. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/LICENSE +0 -0
  42. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/README.md +0 -0
  43. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/SECURITY.md +0 -0
  44. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/hooks/hooks.json +0 -0
  45. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/hooks/session-start.sh +0 -0
  46. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/renovate.json +0 -0
  47. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/scripts/enforce-commit.sh +0 -0
  48. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/semantic-release.toml +0 -0
  49. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/refactor-check/SKILL.md +0 -0
  50. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/review-delta/SKILL.md +0 -0
  51. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/review-pr/SKILL.md +0 -0
  52. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/__init__.py +0 -0
  53. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/__main__.py +0 -0
  54. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/cli.py +0 -0
  55. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/embeddings.py +0 -0
  56. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/graph.py +0 -0
  57. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/py.typed +0 -0
  58. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/server.py +0 -0
  59. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/__init__.py +0 -0
  60. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/Sample.cs +0 -0
  61. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/SampleJava.java +0 -0
  62. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/caller_example.py +0 -0
  63. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/multi_call_example.py +0 -0
  64. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.c +0 -0
  65. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.cpp +0 -0
  66. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.kt +0 -0
  67. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.php +0 -0
  68. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.rb +0 -0
  69. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.sol +0 -0
  70. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.swift +0 -0
  71. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_go.go +0 -0
  72. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_python.py +0 -0
  73. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_rust.rs +0 -0
  74. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_typescript.ts +0 -0
  75. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/test_sample.py +0 -0
  76. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_cli.py +0 -0
  77. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_embeddings.py +0 -0
  78. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_embeddings_extra.py +0 -0
  79. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_full_live.py +0 -0
  80. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_graph.py +0 -0
  81. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_incremental.py +0 -0
  82. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_incremental_extra.py +0 -0
  83. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_live_mcp.py +0 -0
  84. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_multilang.py +0 -0
  85. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_parser.py +0 -0
  86. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_parser_extra.py +0 -0
  87. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_server.py +0 -0
  88. {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_tools.py +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "better-code-review-graph",
3
3
  "description": "Persistent incremental knowledge graph for token-efficient code reviews",
4
- "version": "3.2.0",
4
+ "version": "3.3.0",
5
5
  "author": {
6
6
  "name": "n24q02m",
7
7
  "url": "https://github.com/n24q02m"
@@ -40,22 +40,29 @@ jobs:
40
40
  with:
41
41
  egress-policy: audit
42
42
 
43
+ - name: Generate GitHub App Token
44
+ id: app-token
45
+ uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
46
+ with:
47
+ app-id: ${{ vars.CI_APP_ID }}
48
+ private-key: ${{ secrets.CI_APP_KEY }}
49
+
43
50
  - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
44
51
  with:
45
- token: ${{ secrets.GH_PAT }}
52
+ token: ${{ steps.app-token.outputs.token }}
46
53
  fetch-depth: 0
47
54
 
48
55
  - uses: python-semantic-release/python-semantic-release@350c48fcb3ffcdfd2e0a235206bc2ecea6b69df0 # v10
49
56
  id: release
50
57
  with:
51
- github_token: ${{ secrets.GH_PAT }}
58
+ github_token: ${{ steps.app-token.outputs.token }}
52
59
  prerelease: ${{ inputs.release_type == 'beta' }}
53
60
  prerelease_token: beta
54
61
 
55
62
  - uses: python-semantic-release/publish-action@310a9983a0ae878b29f3aac778d7c77c1db27378 # v10
56
63
  if: steps.release.outputs.released == 'true'
57
64
  with:
58
- github_token: ${{ secrets.GH_PAT }}
65
+ github_token: ${{ steps.app-token.outputs.token }}
59
66
  tag: ${{ steps.release.outputs.tag }}
60
67
 
61
68
  # =================== Publish to PyPI ===================
@@ -77,8 +84,6 @@ jobs:
77
84
  - run: uv build
78
85
  - name: Publish to PyPI
79
86
  run: uv publish
80
- env:
81
- UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
82
87
 
83
88
  # =================== Build Docker ===================
84
89
  build-docker:
@@ -66,10 +66,9 @@ jobs:
66
66
 
67
67
  - name: Upload coverage to Codecov
68
68
  if: github.event_name == 'push'
69
- uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
69
+ uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
70
70
  with:
71
71
  files: coverage.xml
72
- token: ${{ secrets.CODECOV_TOKEN }}
73
72
 
74
73
  codeql:
75
74
  name: CodeQL Analysis
@@ -91,12 +90,12 @@ jobs:
91
90
  uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
92
91
 
93
92
  - name: Initialize CodeQL
94
- uses: github/codeql-action/init@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
93
+ uses: github/codeql-action/init@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3
95
94
  with:
96
95
  languages: python
97
96
 
98
97
  - name: Perform CodeQL Analysis
99
- uses: github/codeql-action/analyze@ebcb5b36ded6beda4ceefea6a8bc4cc885255bb3 # v3
98
+ uses: github/codeql-action/analyze@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3
100
99
 
101
100
  dependency-review:
102
101
  name: Dependency Review
@@ -134,6 +133,7 @@ jobs:
134
133
  issues: write
135
134
  pull-requests: write
136
135
  contents: read
136
+ id-token: write
137
137
  steps:
138
138
  - name: Checkout repo
139
139
  uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -148,11 +148,18 @@ jobs:
148
148
  echo "$RANDOM_EOF" >> "$GITHUB_ENV"
149
149
  fi
150
150
 
151
+ - name: Authenticate to GCP (Vertex AI)
152
+ uses: google-github-actions/auth@v2
153
+ with:
154
+ workload_identity_provider: ${{ vars.VERTEX_WIF_PROVIDER }}
155
+ service_account: ${{ vars.VERTEX_SERVICE_ACCOUNT }}
156
+
151
157
  - name: PR Agent action step
152
158
  uses: qodo-ai/pr-agent@42d55d4182a4839820b11b6c4d06fce855970301 # main
153
159
  env:
154
160
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
155
- GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
161
+ VERTEXAI_PROJECT: ${{ vars.VERTEX_PROJECT }}
162
+ VERTEXAI_LOCATION: us-central1
156
163
  PR_REVIEWER__EXTRA_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }}
157
164
  PR_CODE_SUGGESTIONS__EXTRA_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }}
158
165
  PR_DESCRIPTION__EXTRA_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }}
@@ -238,16 +245,23 @@ jobs:
238
245
  } >> "$GITHUB_OUTPUT"
239
246
  fi
240
247
 
248
+ - name: Parse SMTP credential
249
+ run: |
250
+ echo "SMTP_USER=${SMTP_CREDENTIAL%%:*}" >> $GITHUB_ENV
251
+ echo "SMTP_PASS=${SMTP_CREDENTIAL#*:}" >> $GITHUB_ENV
252
+ env:
253
+ SMTP_CREDENTIAL: ${{ secrets.SMTP_CREDENTIAL }}
254
+
241
255
  - name: Send email notification
242
256
  uses: dawidd6/action-send-mail@d38f3f7cd391cdebfe0d38efc3998b935e951c4f # v16
243
257
  with:
244
258
  server_address: smtp.gmail.com
245
259
  server_port: 587
246
- username: ${{ secrets.SMTP_USERNAME }}
247
- password: ${{ secrets.SMTP_PASSWORD }}
260
+ username: ${{ env.SMTP_USER }}
261
+ password: ${{ env.SMTP_PASS }}
248
262
  subject: "[${{ github.event.repository.name }}] ${{ steps.data.outputs.event_type }} #${{ steps.data.outputs.number }}: ${{ steps.data.outputs.title }}"
249
- to: ${{ secrets.NOTIFY_EMAIL }}
250
- from: GitHub Notify <${{ secrets.SMTP_USERNAME }}>
263
+ to: ${{ env.SMTP_USER }}
264
+ from: GitHub Notify <${{ env.SMTP_USER }}>
251
265
  body: |
252
266
  New ${{ steps.data.outputs.event_type }} in ${{ github.repository }}
253
267
 
@@ -32,6 +32,10 @@ env/
32
32
  .DS_Store
33
33
  Thumbs.db
34
34
 
35
+ # Secrets / env
36
+ .env
37
+ .env.*
38
+
35
39
  # Claude Code
36
40
  .claude/
37
41
 
@@ -0,0 +1,4 @@
1
+ ## 2023-10-24 - [Path Traversal in tools.py and incremental.py]
2
+ **Vulnerability:** Found Path Traversal vulnerability where user input (file paths) was concatenated to paths (`root / rel_path`) without ensuring they don't escape the project root directory.
3
+ **Learning:** Concatenating path components using `/` without checking for bounds allows reading or modifying unauthorized system files.
4
+ **Prevention:** To prevent this, always build paths using `(base_path / user_path).resolve()` and explicitly verify boundaries using `if not resolved_path.is_relative_to(base_path.resolve()): continue`.
@@ -15,7 +15,7 @@ repos:
15
15
  # Security: Secret Detection (MUST be first — blocks commit if secrets found)
16
16
  # ============================================================================
17
17
  - repo: https://github.com/gitleaks/gitleaks
18
- rev: v8.30.1
18
+ rev: 83d9cd684c87d95d656c1458ef04895a7f1cbd8e # v8.30.1
19
19
  hooks:
20
20
  - id: gitleaks
21
21
  name: gitleaks (detect secrets)
@@ -26,7 +26,7 @@ repos:
26
26
 
27
27
  # Ruff: Fast Python linter and formatter
28
28
  - repo: https://github.com/astral-sh/ruff-pre-commit
29
- rev: v0.15.7
29
+ rev: e05c5c0818279e5ac248ac9e954431ba58865e61 # v0.15.7
30
30
  hooks:
31
31
  - id: ruff
32
32
  name: ruff lint
@@ -59,7 +59,7 @@ repos:
59
59
  # ============================================================================
60
60
 
61
61
  - repo: https://github.com/pre-commit/pre-commit-hooks
62
- rev: v5.0.0
62
+ rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # v5.0.0
63
63
  hooks:
64
64
  - id: trailing-whitespace
65
65
  name: trim trailing whitespace
@@ -1,12 +1,12 @@
1
1
  # syntax=docker/dockerfile:1
2
- FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder
2
+ FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim@sha256:531f855bda2c73cd6ef67d56b733b357cea384185b3022bd09f05e002cd144ca AS builder
3
3
  WORKDIR /app
4
4
  COPY pyproject.toml uv.lock README.md ./
5
5
  RUN --mount=type=cache,target=/root/.cache/uv \
6
6
  uv sync --frozen --no-dev --no-editable
7
7
  COPY src/ src/
8
8
 
9
- FROM python:3.13-slim-bookworm
9
+ FROM python:3.13-slim-bookworm@sha256:01f42367a0a94ad4bc17111776fd66e3500c1d87c15bbd6055b7371d39c124fb
10
10
  LABEL io.modelcontextprotocol.server.name="io.github.n24q02m/better-code-review-graph"
11
11
  RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
12
12
  WORKDIR /app
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: better-code-review-graph
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Knowledge graph for token-efficient code reviews — fixed search, configurable embeddings, qualified call resolution
5
5
  Author: n24q02m
6
6
  License-Expression: MIT
@@ -11,19 +11,19 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Classifier: Topic :: Software Development :: Quality Assurance
13
13
  Requires-Python: ==3.13.*
14
- Requires-Dist: cohere>=5.20
15
- Requires-Dist: fastmcp<3,>=2.14.2
16
- Requires-Dist: google-genai>=1.0
14
+ Requires-Dist: cohere>=5.21.1
15
+ Requires-Dist: fastmcp<3,>=2.14.5
16
+ Requires-Dist: google-genai>=1.68.0
17
17
  Requires-Dist: httpx
18
- Requires-Dist: mcp-relay-core>=0.1.0
19
- Requires-Dist: mcp<2,>=1.0.0
20
- Requires-Dist: networkx<4,>=3.2
21
- Requires-Dist: openai>=1.50
18
+ Requires-Dist: mcp-relay-core>=1.0.5
19
+ Requires-Dist: mcp<2,>=1.26.0
20
+ Requires-Dist: networkx<4,>=3.6.1
21
+ Requires-Dist: openai>=1.109.1
22
22
  Requires-Dist: pydantic-settings
23
- Requires-Dist: qwen3-embed>=1.5.0
24
- Requires-Dist: tree-sitter-language-pack<1,>=0.3.0
25
- Requires-Dist: tree-sitter<1,>=0.23.0
26
- Requires-Dist: watchdog<6,>=4.0.0
23
+ Requires-Dist: qwen3-embed>=1.5.1
24
+ Requires-Dist: tree-sitter-language-pack<1,>=0.13.0
25
+ Requires-Dist: tree-sitter<1,>=0.25.2
26
+ Requires-Dist: watchdog<6,>=4.0.2
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # Better Code Review Graph
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-code-review-graph",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Persistent incremental knowledge graph for token-efficient code reviews",
5
5
  "mcpServers": {
6
6
  "better-code-review-graph": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "better-code-review-graph"
7
- version = "3.2.0"
7
+ version = "3.3.0"
8
8
  description = "Knowledge graph for token-efficient code reviews — fixed search, configurable embeddings, qualified call resolution"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "MIT"
@@ -18,19 +18,19 @@ classifiers = [
18
18
  "Topic :: Software Development :: Quality Assurance",
19
19
  ]
20
20
  dependencies = [
21
- "mcp>=1.0.0,<2",
22
- "fastmcp>=2.14.2,<3",
23
- "tree-sitter>=0.23.0,<1",
24
- "tree-sitter-language-pack>=0.3.0,<1",
25
- "networkx>=3.2,<4",
26
- "watchdog>=4.0.0,<6",
27
- "qwen3-embed>=1.5.0",
28
- "cohere>=5.20",
29
- "google-genai>=1.0",
30
- "openai>=1.50",
21
+ "mcp>=1.26.0,<2",
22
+ "fastmcp>=2.14.5,<3",
23
+ "tree-sitter>=0.25.2,<1",
24
+ "tree-sitter-language-pack>=0.13.0,<1",
25
+ "networkx>=3.6.1,<4",
26
+ "watchdog>=4.0.2,<6",
27
+ "qwen3-embed>=1.5.1",
28
+ "cohere>=5.21.1",
29
+ "google-genai>=1.68.0",
30
+ "openai>=1.109.1",
31
31
  "httpx",
32
32
  "pydantic-settings",
33
- "mcp-relay-core>=0.1.0",
33
+ "mcp-relay-core>=1.0.5",
34
34
  ]
35
35
 
36
36
  [project.scripts]
@@ -38,13 +38,13 @@ better-code-review-graph = "better_code_review_graph.cli:main"
38
38
 
39
39
  [dependency-groups]
40
40
  dev = [
41
- "pytest>=8.0,<9",
42
- "pytest-asyncio>=0.23,<1",
41
+ "pytest>=8.4.2,<9",
42
+ "pytest-asyncio>=0.26.0,<1",
43
43
  "pytest-cov",
44
44
  "pytest-timeout",
45
- "ruff>=0.8.6",
45
+ "ruff>=0.15.8",
46
46
  "syrupy",
47
- "ty>=0.0.1a10",
47
+ "ty>=0.0.26",
48
48
  ]
49
49
 
50
50
  [tool.hatch.build.targets.wheel]
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/n24q02m/better-code-review-graph.git",
7
7
  "source": "github"
8
8
  },
9
- "version": "3.2.0",
9
+ "version": "3.3.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "pypi",
13
13
  "identifier": "better-code-review-graph",
14
- "version": "3.2.0",
14
+ "version": "3.3.0",
15
15
  "runtimeHint": "uvx",
16
16
  "runtimeArgs": [
17
17
  "--python",
@@ -214,10 +214,13 @@ def collect_all_files(repo_root: Path) -> list[str]:
214
214
  for rel_path in candidates:
215
215
  if _should_ignore(rel_path, ignore_patterns):
216
216
  continue
217
- full_path = repo_root / rel_path
217
+ full_path_raw = repo_root / rel_path
218
+ full_path = full_path_raw.resolve()
219
+ if not full_path.is_relative_to(repo_root.resolve()):
220
+ continue
218
221
  if not full_path.is_file():
219
222
  continue
220
- if full_path.is_symlink():
223
+ if full_path_raw.is_symlink() or full_path.is_symlink():
221
224
  continue
222
225
  if parser.detect_language(full_path) is None:
223
226
  continue
@@ -269,7 +272,9 @@ def full_build(repo_root: Path, store: GraphStore) -> dict:
269
272
  file_count = len(files)
270
273
 
271
274
  for i, rel_path in enumerate(files, 1):
272
- full_path = repo_root / rel_path
275
+ full_path = (repo_root / rel_path).resolve()
276
+ if not full_path.is_relative_to(repo_root.resolve()):
277
+ continue
273
278
  try:
274
279
  source = full_path.read_bytes()
275
280
  fhash = hashlib.sha256(source).hexdigest()
@@ -342,11 +347,16 @@ def incremental_update(
342
347
  for rel_path in all_files:
343
348
  if _should_ignore(rel_path, ignore_patterns):
344
349
  continue
345
- abs_path = repo_root / rel_path
350
+ abs_path_raw = repo_root / rel_path
351
+ abs_path = abs_path_raw.resolve()
352
+ if not abs_path.is_relative_to(repo_root.resolve()):
353
+ continue
346
354
  if not abs_path.is_file():
347
355
  # File was deleted
348
356
  store.remove_file_data(str(abs_path))
349
357
  continue
358
+ if abs_path_raw.is_symlink() or abs_path.is_symlink():
359
+ continue
350
360
  if parser.detect_language(abs_path) is None:
351
361
  continue
352
362
 
@@ -990,71 +990,105 @@ class CodeParser:
990
990
 
991
991
  def _extract_import(self, node, language: str, source: bytes) -> list[str]:
992
992
  """Extract import targets as module/path strings."""
993
- imports = []
994
993
  text = node.text.decode("utf-8", errors="replace").strip()
995
994
 
996
995
  if language == "python":
997
- # import x.y.z or from x.y import z
998
- if node.type == "import_from_statement":
999
- for child in node.children:
1000
- if child.type == "dotted_name":
1001
- imports.append(child.text.decode("utf-8", errors="replace"))
1002
- break
1003
- else:
1004
- for child in node.children:
1005
- if child.type == "dotted_name":
1006
- imports.append(child.text.decode("utf-8", errors="replace"))
996
+ return self._extract_import_python(node)
1007
997
  elif language in ("javascript", "typescript", "tsx"):
1008
- # import ... from 'module'
1009
- for child in node.children:
1010
- if child.type == "string":
1011
- val = child.text.decode("utf-8", errors="replace").strip("'\"")
1012
- imports.append(val)
998
+ return self._extract_import_javascript(node)
1013
999
  elif language == "go":
1014
- for child in node.children:
1015
- if child.type == "import_spec_list":
1016
- for spec in child.children:
1017
- if spec.type == "import_spec":
1018
- for s in spec.children:
1019
- if s.type == "interpreted_string_literal":
1020
- val = s.text.decode("utf-8", errors="replace")
1021
- imports.append(val.strip('"'))
1022
- elif child.type == "import_spec":
1023
- for s in child.children:
1024
- if s.type == "interpreted_string_literal":
1025
- val = s.text.decode("utf-8", errors="replace")
1026
- imports.append(val.strip('"'))
1000
+ return self._extract_import_go(node)
1027
1001
  elif language == "rust":
1028
- # use crate::module::item
1029
- imports.append(text.replace("use ", "").rstrip(";").strip())
1002
+ return self._extract_import_rust(text)
1030
1003
  elif language in ("c", "cpp"):
1031
- # #include <header> or #include "header"
1032
- for child in node.children:
1033
- if child.type in ("system_lib_string", "string_literal"):
1034
- val = child.text.decode("utf-8", errors="replace").strip('<>"')
1035
- imports.append(val)
1004
+ return self._extract_import_c(node)
1036
1005
  elif language in ("java", "csharp"):
1037
- # import/using package.Class
1038
- parts = text.split()
1039
- if len(parts) >= 2:
1040
- imports.append(parts[-1].rstrip(";"))
1006
+ return self._extract_import_java(text)
1041
1007
  elif language == "solidity":
1042
- # import "path/to/file.sol" or import {Symbol} from "path"
1043
- for child in node.children:
1044
- if child.type == "string":
1045
- val = child.text.decode("utf-8", errors="replace").strip('"')
1046
- if val:
1047
- imports.append(val)
1008
+ return self._extract_import_solidity(node)
1048
1009
  elif language == "ruby":
1049
- # require 'module' or require_relative 'path'
1050
- if "require" in text:
1051
- match = re.search(r"""['"](.*?)['"]""", text)
1052
- if match:
1053
- imports.append(match.group(1))
1010
+ return self._extract_import_ruby(text)
1054
1011
  else:
1055
- # Fallback: just record the text
1056
- imports.append(text)
1012
+ return [text]
1057
1013
 
1014
+ def _extract_import_python(self, node) -> list[str]:
1015
+ imports = []
1016
+ # import x.y.z or from x.y import z
1017
+ if node.type == "import_from_statement":
1018
+ for child in node.children:
1019
+ if child.type == "dotted_name":
1020
+ imports.append(child.text.decode("utf-8", errors="replace"))
1021
+ break
1022
+ else:
1023
+ for child in node.children:
1024
+ if child.type == "dotted_name":
1025
+ imports.append(child.text.decode("utf-8", errors="replace"))
1026
+ return imports
1027
+
1028
+ def _extract_import_javascript(self, node) -> list[str]:
1029
+ imports = []
1030
+ # import ... from 'module'
1031
+ for child in node.children:
1032
+ if child.type == "string":
1033
+ val = child.text.decode("utf-8", errors="replace").strip("'\"")
1034
+ imports.append(val)
1035
+ return imports
1036
+
1037
+ def _extract_import_go(self, node) -> list[str]:
1038
+ imports = []
1039
+ for child in node.children:
1040
+ if child.type == "import_spec_list":
1041
+ for spec in child.children:
1042
+ if spec.type == "import_spec":
1043
+ for s in spec.children:
1044
+ if s.type == "interpreted_string_literal":
1045
+ val = s.text.decode("utf-8", errors="replace")
1046
+ imports.append(val.strip('"'))
1047
+ elif child.type == "import_spec":
1048
+ for s in child.children:
1049
+ if s.type == "interpreted_string_literal":
1050
+ val = s.text.decode("utf-8", errors="replace")
1051
+ imports.append(val.strip('"'))
1052
+ return imports
1053
+
1054
+ def _extract_import_rust(self, text: str) -> list[str]:
1055
+ # use crate::module::item
1056
+ return [text.replace("use ", "").rstrip(";").strip()]
1057
+
1058
+ def _extract_import_c(self, node) -> list[str]:
1059
+ imports = []
1060
+ # #include <header> or #include "header"
1061
+ for child in node.children:
1062
+ if child.type in ("system_lib_string", "string_literal"):
1063
+ val = child.text.decode("utf-8", errors="replace").strip('<>"')
1064
+ imports.append(val)
1065
+ return imports
1066
+
1067
+ def _extract_import_java(self, text: str) -> list[str]:
1068
+ imports = []
1069
+ # import/using package.Class
1070
+ parts = text.split()
1071
+ if len(parts) >= 2:
1072
+ imports.append(parts[-1].rstrip(";"))
1073
+ return imports
1074
+
1075
+ def _extract_import_solidity(self, node) -> list[str]:
1076
+ imports = []
1077
+ # import "path/to/file.sol" or import {Symbol} from "path"
1078
+ for child in node.children:
1079
+ if child.type == "string":
1080
+ val = child.text.decode("utf-8", errors="replace").strip('"')
1081
+ if val:
1082
+ imports.append(val)
1083
+ return imports
1084
+
1085
+ def _extract_import_ruby(self, text: str) -> list[str]:
1086
+ imports = []
1087
+ # require 'module' or require_relative 'path'
1088
+ if "require" in text:
1089
+ match = re.search(r"""['"](.*?)['"]""", text)
1090
+ if match:
1091
+ imports.append(match.group(1))
1058
1092
  return imports
1059
1093
 
1060
1094
  def _get_call_name(self, node, language: str, source: bytes) -> str | None:
@@ -0,0 +1,47 @@
1
+ """Config schema for relay page setup."""
2
+
3
+ from mcp_relay_core.schema.types import ConfigField, RelayConfigSchema
4
+
5
+ RELAY_SCHEMA: RelayConfigSchema = {
6
+ "server": "better-code-review-graph",
7
+ "displayName": "Code Review Graph",
8
+ "description": "At least one API key required for cloud embeddings. Priority: Jina > Gemini > OpenAI > Cohere. Skip for local ONNX mode.",
9
+ "fields": [
10
+ ConfigField(
11
+ key="JINA_AI_API_KEY",
12
+ label="Jina AI API Key",
13
+ type="password",
14
+ placeholder="jina_...",
15
+ helpUrl="https://jina.ai/embeddings/",
16
+ helpText="Highest priority. Embedding + reranking.",
17
+ required=False,
18
+ ),
19
+ ConfigField(
20
+ key="GEMINI_API_KEY",
21
+ label="Gemini API Key",
22
+ type="password",
23
+ placeholder="AIza...",
24
+ helpUrl="https://aistudio.google.com/apikey",
25
+ helpText="Embedding via Google Gemini. Free tier available.",
26
+ required=False,
27
+ ),
28
+ ConfigField(
29
+ key="OPENAI_API_KEY",
30
+ label="OpenAI API Key",
31
+ type="password",
32
+ placeholder="sk-...",
33
+ helpUrl="https://platform.openai.com/api-keys",
34
+ helpText="Embedding only.",
35
+ required=False,
36
+ ),
37
+ ConfigField(
38
+ key="COHERE_API_KEY",
39
+ label="Cohere API Key",
40
+ type="password",
41
+ placeholder="",
42
+ helpUrl="https://dashboard.cohere.com/api-keys",
43
+ helpText="Embedding + reranking.",
44
+ required=False,
45
+ ),
46
+ ],
47
+ }