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.
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.claude-plugin/plugin.json +1 -1
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/workflows/cd.yml +10 -5
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/workflows/ci.yml +23 -9
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.gitignore +4 -0
- better_code_review_graph-3.3.0/.jules/sentinel.md +4 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.pre-commit-config.yaml +3 -3
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/Dockerfile +2 -2
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/PKG-INFO +12 -12
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/gemini-extension.json +1 -1
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/pyproject.toml +16 -16
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/server.json +2 -2
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/incremental.py +14 -4
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/parser.py +87 -53
- better_code_review_graph-3.3.0/src/better_code_review_graph/relay_schema.py +47 -0
- better_code_review_graph-3.3.0/src/better_code_review_graph/relay_setup.py +125 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/tools.py +6 -1
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/conftest.py +10 -0
- better_code_review_graph-3.3.0/tests/test_coverage_gaps.py +695 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_graph_extra.py +2 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_relay.py +21 -35
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_tools_full.py +39 -14
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/uv.lock +69 -69
- better_code_review_graph-3.2.0/src/better_code_review_graph/relay_schema.py +0 -18
- better_code_review_graph-3.2.0/src/better_code_review_graph/relay_setup.py +0 -88
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/build-graph/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/review-delta/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.agents/skills/review-pr/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.dockerignore +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/CODEOWNERS +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/best_practices.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.github/rulesets/main.json +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/.infisical.json +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/AGENTS.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CHANGELOG.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CLAUDE.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CODE_OF_CONDUCT.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/CONTRIBUTING.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/LICENSE +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/README.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/SECURITY.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/hooks/hooks.json +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/hooks/session-start.sh +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/renovate.json +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/scripts/enforce-commit.sh +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/semantic-release.toml +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/refactor-check/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/review-delta/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/skills/review-pr/SKILL.md +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/__init__.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/__main__.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/cli.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/embeddings.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/graph.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/py.typed +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/src/better_code_review_graph/server.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/__init__.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/Sample.cs +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/SampleJava.java +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/caller_example.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/multi_call_example.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.c +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.cpp +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.kt +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.php +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.rb +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.sol +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample.swift +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_go.go +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_python.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_rust.rs +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/sample_typescript.ts +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/fixtures/test_sample.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_cli.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_embeddings.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_embeddings_extra.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_full_live.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_graph.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_incremental.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_incremental_extra.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_live_mcp.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_multilang.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_parser.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_parser_extra.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_server.py +0 -0
- {better_code_review_graph-3.2.0 → better_code_review_graph-3.3.0}/tests/test_tools.py +0 -0
|
@@ -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: ${{
|
|
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: ${{
|
|
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: ${{
|
|
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@
|
|
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@
|
|
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@
|
|
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
|
-
|
|
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: ${{
|
|
247
|
-
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: ${{
|
|
250
|
-
from: GitHub Notify <${{
|
|
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
|
|
|
@@ -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.
|
|
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.
|
|
15
|
-
Requires-Dist: fastmcp<3,>=2.14.
|
|
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>=
|
|
19
|
-
Requires-Dist: mcp<2,>=1.
|
|
20
|
-
Requires-Dist: networkx<4,>=3.
|
|
21
|
-
Requires-Dist: openai>=1.
|
|
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.
|
|
24
|
-
Requires-Dist: tree-sitter-language-pack<1,>=0.
|
|
25
|
-
Requires-Dist: tree-sitter<1,>=0.
|
|
26
|
-
Requires-Dist: watchdog<6,>=4.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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "better-code-review-graph"
|
|
7
|
-
version = "3.
|
|
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.
|
|
22
|
-
"fastmcp>=2.14.
|
|
23
|
-
"tree-sitter>=0.
|
|
24
|
-
"tree-sitter-language-pack>=0.
|
|
25
|
-
"networkx>=3.
|
|
26
|
-
"watchdog>=4.0.
|
|
27
|
-
"qwen3-embed>=1.5.
|
|
28
|
-
"cohere>=5.
|
|
29
|
-
"google-genai>=1.0",
|
|
30
|
-
"openai>=1.
|
|
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>=
|
|
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.
|
|
42
|
-
"pytest-asyncio>=0.
|
|
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
|
|
45
|
+
"ruff>=0.15.8",
|
|
46
46
|
"syrupy",
|
|
47
|
-
"ty>=0.0.
|
|
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.
|
|
9
|
+
"version": "3.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "pypi",
|
|
13
13
|
"identifier": "better-code-review-graph",
|
|
14
|
-
"version": "3.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1029
|
-
imports.append(text.replace("use ", "").rstrip(";").strip())
|
|
1002
|
+
return self._extract_import_rust(text)
|
|
1030
1003
|
elif language in ("c", "cpp"):
|
|
1031
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|