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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Preferred: run `codeturtle` and save to ~/.codeturtle/config.toml
# This file is optional (cwd .env). Config.toml is read first.
GITHUB_TOKEN=
OLLAMA_MODEL=qwen2.5:7b
OLLAMA_BASE_URL=http://localhost:11434
Expand All @@ -6,4 +8,3 @@ LLM_BACKEND=ollama
# OPENAI_API_KEY=
# LANGFUSE_PUBLIC_KEY=
# LANGFUSE_SECRET_KEY=
# GRAPHIFY_GRAPH_PATH=
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Venkat Pachala

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
85 changes: 42 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
# CodeTurtle

Local-first GitHub pull request reviewer.
Local-first GitHub PR reviewer. Graphify structure, clamped **MERGE** / **COMMENT** / **REQUEST_CHANGES**. Runs on your machine.

Deterministic gates (PR facts, path grounding, hunk verification, coverage clamp)
plus a specialist agent swarm. Structure comes from Graphify. Reviews run on
your machine. Nothing is posted unless you pass `--comment`.
```bash
uv tool install "git+https://github.com/venkatpachala/CodeTurtle.git@v0.3.0"
codeturtle
```

Requires: **Ollama** (or `OPENAI_API_KEY`), **git**, and a **GitHub token** (`gh auth login` or paste once).

`codeturtle` with no arguments opens a menu: save token → pick an Ollama model → type `owner/repo` and a PR number. The CLI clones into `~/.codeturtle/repos/`, builds a Graphify code-only graph if needed, and dry-runs the review.

```text
CodeTurtle
[1] GitHub token detected / paste once → ~/.codeturtle/config.toml
[2] Model list from Ollama: qwen2.5:7b, llama3.2, ...
[3] Review a PR owner/repo 3292
[4] Quit
```

Scripts still work:

```bash
uv tool install git+https://github.com/venkatpachala/CodeTurtle.git@v0.2.0
codeturtle review confident-ai/deepeval 3292
codeturtle review owner/repo 123 --dry-run
```

Default is **dry-run**. Nothing is posted unless you pass `--comment`. Qdrant and Neo4j are not required.

---

## What is CodeTurtle?

CodeTurtle is a CLI. It fetches a GitHub PR with your token, builds a
code-only Graphify graph of the repository, reviews **change units** (hunks)
instead of a truncated diff, and emits **MERGE**, **COMMENT**, or
**REQUEST_CHANGES**.

Config lives in `%USERPROFILE%\.codeturtle\config.toml` (or `~/.codeturtle/config.toml`), not a random project `.env`.

Agents propose findings. Gates decide what ships:

- a finding that only cites `.github/wordlist.txt` is dropped
Expand Down Expand Up @@ -54,63 +75,42 @@ The model does not own file selection, line identity, or the final GitHub event
### Prerequisites

- Python >= 3.11
- GitHub token with `public_repo` (or fine-grained PR read)
- An LLM: Ollama (default) or OpenAI-compatible
- Graphify code-only graph for the repo you review
- git
- GitHub token with `public_repo` (or fine-grained PR read), or `gh auth login`
- An LLM: [Ollama](https://ollama.com/download) (`ollama pull qwen2.5:7b`) or `OPENAI_API_KEY`

### Install
You cannot ship a 7B model inside `uv tool install`. Ollama (or an API key) is the only extra install.

```bash
uv tool install git+https://github.com/venkatpachala/CodeTurtle.git@v0.2.0

# from a clone
git clone https://github.com/venkatpachala/CodeTurtle.git
cd CodeTurtle
pip install -e ".[ollama,graphify]"
codeturtle --help
```

### Setup
### Install

```bash
cp .env.example .env
# set GITHUB_TOKEN
# OLLAMA_MODEL=qwen2.5:7b
ollama pull qwen2.5:7b
uv tool install "git+https://github.com/venkatpachala/CodeTurtle.git@v0.3.0"
codeturtle
```

Index structure once per repo:
From a clone:

```bash
git clone https://github.com/owner/repo repos/owner_repo
cd repos/owner_repo
graphify . --code-only
git clone https://github.com/venkatpachala/CodeTurtle.git
cd CodeTurtle
pip install -e .
codeturtle --help
```

Qdrant and Neo4j are not required for `--help` or `--dry-run`.
Graphify is included in the default install.

### Review
### Commands

```bash
codeturtle new-session
codeturtle
codeturtle review owner/repo 123 --dry-run
codeturtle review owner/repo 123 --comment
codeturtle graphify-test owner/repo --stats
```

Default is dry-run. `--comment` on a repo without write access fails closed and prints the body.

### Smoke test (v0.2.0)

| Field | Value |
|-------|--------|
| PR | [confident-ai/deepeval#3288](https://github.com/confident-ai/deepeval/pull/3288) |
| Classification | source |
| Change units | 2 |
| Investigate | 6 hops |
| Decision | REQUEST_CHANGES |
| Graphify | ~19k nodes / ~57k edges |
| Post | none (`--dry-run`) |
### Dev eval

```bash
uv run python -m tests.evaluation.run_eval --offline
Expand Down Expand Up @@ -139,4 +139,3 @@ scripts/ # maintainer helpers
## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).

3 changes: 3 additions & 0 deletions cli/commands/graphify_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def graphify_test(
):
"""Handshake Graphify MCP for a specific repository graph."""
try:
from core.workspace import ensure_workspace

ensure_workspace(repo)
graph_path = resolve_graph_path(repo)
provider = get_knowledge_provider(repo=repo)

Expand Down
5 changes: 4 additions & 1 deletion cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ def init():
token = Prompt.ask("Enter your GitHub Token (or press Enter to use GITHUB_TOKEN env)")
if token:
os.environ["GITHUB_TOKEN"] = token
from core.user_config import save_user_config

save_user_config({"github_token": token})

console.print("[green]✓[/green] CodeTurtle initialized successfully!")
console.print("You can now run: [bold]codeturtle review owner/repo PR_NUMBER[/bold]")
console.print("You can now run: [bold]codeturtle[/bold] or [bold]codeturtle review owner/repo PR_NUMBER[/bold]")
16 changes: 15 additions & 1 deletion cli/commands/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ class PipelineContext:

def get_current_session() -> str:
from core.ci import ensure_session
from core.user_config import session_file

return ensure_session()
path = session_file()
path.parent.mkdir(parents=True, exist_ok=True)
return ensure_session(str(path))


def _as_dict(obj: Any) -> dict:
Expand Down Expand Up @@ -166,6 +169,17 @@ def run(
)
)

from core.ci import resolve_github_token
from core.workspace import WorkspaceError, ensure_workspace

token = resolve_github_token(fallback=str(settings.github_token or ""))
try:
graph = ensure_workspace(repo, number, token=token)
print(f"[Workspace] graph={graph}")
except WorkspaceError as exc:
console.print(f"[red]{exc}[/red]")
raise SystemExit(1) from exc

self._load_knowledge_base()
self._fetch_pr()
self._build_full_diff()
Expand Down
8 changes: 5 additions & 3 deletions cli/commands/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ def new_session():
try:
conversation_id = memory.create_new_session()

# Save as current active session
try:
with open(".current_session", "w") as f:
f.write(conversation_id)
from core.user_config import session_file

path = session_file()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(conversation_id + "\n", encoding="utf-8")
except Exception as e:
raise Exception("Failed to save current session") from e

Expand Down
Loading
Loading