Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install ruff
- run: ruff format --check .
- run: ruff check .
- uses: astral-sh/setup-uv@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install dependencies
run: uv sync

- name: Format Check (Ruff)
run: uv run ruff format --check .

- name: Lint Check (Ruff)
run: uv run ruff check .

- name: Type Check (Pyright)
run: uv run pyright .

build:
runs-on: ubuntu-latest
Expand Down
8 changes: 2 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "reviewbot"
version = "0.3.0"
version = "0.3.1"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
Expand Down Expand Up @@ -54,10 +54,6 @@ quote-style = "double"
indent-style = "space"

[dependency-groups]
dev = [
"pyright>=1.1.408",
"ruff>=0.8.6",
"ty>=0.0.4",
]
dev = ["pyright>=1.1.408", "ruff>=0.8.6", "ty>=0.0.4"]
[project.optional-dependencies]
examples = ["fastapi"]
4 changes: 3 additions & 1 deletion src/reviewbot/agent/workflow/diff_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import re
from typing import Any

type LineNo = int | None


def generate_line_code(file_path: str, old_line: int | None, new_line: int | None) -> str:
"""
Expand Down Expand Up @@ -31,7 +33,7 @@ def create_position_for_issue(
in_hunk = False

# Track the actual lines found in the diff to build the range
matched_lines = [] # List of (old_line, new_line)
matched_lines: list[tuple[LineNo, LineNo]] = [] # List of (old_line, new_line)

for line in lines:
match = hunk_header_pattern.match(line)
Expand Down
4 changes: 2 additions & 2 deletions src/reviewbot/agent/workflow/ignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def parse_reviewignore(repo_path: Path) -> list[str]:
List of glob patterns to ignore
"""
reviewignore_path = repo_path / ".reviewignore"
patterns = []
patterns: list[str] = []

if not reviewignore_path.exists():
console.print("[dim].reviewignore file not found, using global blacklist only[/dim]")
Expand Down Expand Up @@ -136,7 +136,7 @@ def filter_diffs(diffs: list[FileDiff], reviewignore_patterns: list[str]) -> lis
Returns:
Filtered list of diffs
"""
filtered = []
filtered: list[FileDiff] = []
ignored_count = 0

for diff in diffs:
Expand Down
12 changes: 7 additions & 5 deletions src/reviewbot/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import typer

from reviewbot.agent.workflow import work_agent
from reviewbot.agent.workflow import work_agent # type: ignore
from reviewbot.cli.app import app
from reviewbot.infra.config.env import load_env
from reviewbot.infra.git.repo_tree import tree
Expand All @@ -22,10 +22,12 @@ def work(
mr_iid: str = typer.Argument(..., help="Merge request IID"),
):
config = load_env()
work_agent(
config,
project_id,
mr_iid,
work_agent.invoke( # type: ignore
{
"config": config,
"project_id": project_id,
"mr_iid": mr_iid,
},
)


Expand Down
2 changes: 1 addition & 1 deletion src/reviewbot/core/reviews/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Review:
id: UUID = field(default_factory=uuid4)
repo: str = ""
commit: str = ""
issues: list[Issue] = field(default_factory=list)
issues: list[Issue] = field(default_factory=list[Issue])
summary: str = ""


Expand Down
53 changes: 25 additions & 28 deletions src/reviewbot/infra/embeddings/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pydantic import BaseModel, Field, SecretStr
from rich.console import Console

os.environ["TOKENIZERS_PARALLELISM"] = "false"
Expand Down Expand Up @@ -53,6 +54,14 @@
}


class SearchResult(BaseModel):
similarity: float = Field(..., description="Cosine similarity score (0 to 1)")
path: str = Field(..., description="Relative path to the source file")
filename: str = Field(..., description="Name of the file")
chunk_index: int = Field(..., description="Index of the chunk within the file")
text: str = Field(..., description="The actual code snippet")


class CodebaseVectorStore:
def __init__(
self,
Expand All @@ -62,7 +71,7 @@ def __init__(
vectors_dir: str | Path = "vectors/codebase",
embeddings_model: str | None = None,
embeddings_base_url: str | None = None,
embeddings_api_key: str | None = None,
embeddings_api_key: SecretStr | None = None,
):
self.repo_root = Path(repo_root).resolve()
self.vectors_dir = Path(vectors_dir)
Expand All @@ -73,7 +82,7 @@ def __init__(
if embeddings_base_url is None:
embeddings_base_url = os.getenv("EMBEDDINGS_BASE_URL")
if embeddings_api_key is None:
embeddings_api_key = os.getenv("EMBEDDINGS_API_KEY", "dummy")
embeddings_api_key = SecretStr(os.getenv("EMBEDDINGS_API_KEY", "dummy"))

self.embeddings = OpenAIEmbeddings(
model=embeddings_model,
Expand All @@ -82,7 +91,6 @@ def __init__(
tiktoken_enabled=False,
chunk_size=64,
max_retries=3,
request_timeout=120,
)

self.splitter = RecursiveCharacterTextSplitter(
Expand Down Expand Up @@ -149,22 +157,11 @@ def build(self) -> None:
raise RuntimeError("No source files found to embed")

self.vector_store = FAISS.from_documents(docs, self.embeddings)
self._build_metadata_index()
self.save()

def compile(self, command: str) -> str:
return subprocess.run(command, shell=True, capture_output=True, text=True).stdout

def _build_metadata_index(self) -> None:
self.metadata_index = {}
if not self.vector_store:
return

for doc_id, doc in self.vector_store.docstore._dict.items():
path = doc.metadata.get("path")
if path:
self.metadata_index.setdefault(path, []).append(doc_id)

def save(self) -> None:
if not self.vector_store:
return
Expand Down Expand Up @@ -195,26 +192,26 @@ def search(
*,
top_k: int = 10,
path: str | None = None,
) -> list[dict]:
) -> list[SearchResult]:
if not self.vector_store:
raise RuntimeError("Vector store not loaded")

filter = {}
if path:
filter["path"] = path
results = self.vector_store.similarity_search_with_score(query, k=top_k, filter=filter)
out = []
search_filter = {"path": path} if path else None
results = self.vector_store.similarity_search_with_score( # type: ignore
query, k=top_k, filter=search_filter
)

out: list[SearchResult] = []
for doc, score in results:
out.append(
{
"similarity": float(1 - score),
"path": doc.metadata.get("path"),
"filename": doc.metadata.get("filename"),
"chunk_index": doc.metadata.get("chunk_index"),
"text": doc.page_content,
}
SearchResult(
similarity=float(1 - score),
path=doc.metadata.get("path", ""), # type: ignore
filename=doc.metadata.get("filename", ""), # type: ignore
chunk_index=doc.metadata.get("chunk_index", 0), # type: ignore
text=doc.page_content,
)
)
console.print(out)
return out

def read_file(
Expand Down
Loading