ArshVerma commited on
Commit
9486e76
·
unverified ·
2 Parent(s): 9b070707b1eaf8

Merge pull request #32 from ArshVermaGit/main

Browse files

feat: CodeLens. v1.0.0 — Production-Grade Suite (Docker, CI/CD, Security & Cloud)

.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
.dockerignore ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python Artifacts
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ *.egg
11
+ MANIFEST
12
+
13
+ # Node.js / Dashboard (Exclude sources, only keep builds)
14
+ node_modules/
15
+ dashboard/node_modules/
16
+ dashboard/src/
17
+ dashboard/public/
18
+ dashboard/tests/
19
+ dashboard/*.json
20
+ dashboard/*.config.js
21
+ dashboard/*.config.ts
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ .venv/
26
+ env/
27
+
28
+ # Testing & Coverage
29
+ tests/
30
+ .pytest_cache/
31
+ coverage.xml
32
+ .coverage
33
+ htmlcov/
34
+ pytest.ini
35
+
36
+ # Git
37
+ .git/
38
+ .gitignore
39
+
40
+ # Environment & Private Files
41
+ .env
42
+ .env.*
43
+ *.env.local
44
+ .history/
45
+ Roadmap.html
46
+
47
+ # Data Persistence (Ensures no local DB leaks into image)
48
+ data/
49
+ codelens.db
50
+ *.sqlite3
51
+
52
+ # OS Specific
53
+ .DS_Store
54
+ .DS_Store?
55
+ **/._*
56
+ **/.DS_Store
57
+ Thumbs.db
58
+ ehthumbs.db
59
+
60
+ # IDEs
61
+ .vscode/
62
+ .idea/
63
+ *.swp
64
+ *.swo
.env.example CHANGED
@@ -1,17 +1,25 @@
1
- # AgentOrg CodeReview — Environment Variables
2
  # Copy this file to .env and fill in your values.
3
 
4
- # API Configuration
5
  APP_HOST=0.0.0.0
6
  APP_PORT=7860
7
  APP_ENV=development # development | production
 
 
 
 
 
 
 
 
 
8
 
9
- # Security
10
- API_KEY=changeme # Required in production; sent as X-API-Key header
11
- API_KEY_ENABLED=false # Set to true in production
12
 
13
  # Leaderboard
14
- LEADERBOARD_MAX_ENTRIES=10 # Top-N entries to keep per task
15
 
16
  # Logging
17
  LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR
 
1
+ # CodeLens. — Configuration Template
2
  # Copy this file to .env and fill in your values.
3
 
4
+ # API Profile
5
  APP_HOST=0.0.0.0
6
  APP_PORT=7860
7
  APP_ENV=development # development | production
8
+ APP_PORT=7860
9
+
10
+ # Security (X-API-Key header)
11
+ API_KEY=changeme
12
+ API_KEY_ENABLED=false
13
+
14
+ # Persistence & State
15
+ DATABASE_URL=sqlite+aiosqlite:///./data/codelens.db
16
+ EPISODE_TTL=3600 # Auto-cleanup time in seconds (1hr)
17
 
18
+ # Rate Limiting (Requests per minute)
19
+ RATE_LIMIT_DEFAULT=60
 
20
 
21
  # Leaderboard
22
+ LEADERBOARD_LIMIT=10 # Default entries per task page
23
 
24
  # Logging
25
  LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR
.github/workflows/ci.yml ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop, "feat/**", "fix/**", "test/**", "docs/**" ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ # ── Job 1: Lint ────────────────────────────────────────────────
11
+ lint:
12
+ name: Lint
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ cache: pip
20
+ - run: pip install pylint
21
+ - run: pylint --fail-under=7 $(git ls-files '*.py') || true
22
+ # Soft fail: warn but don't block on lint score
23
+
24
+ # ── Job 2: Test ────────────────────────────────────────────────
25
+ test:
26
+ name: Test (Python ${{ matrix.python-version }})
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ matrix:
30
+ python-version: ["3.10", "3.11"]
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: ${{ matrix.python-version }}
36
+ cache: pip
37
+ - name: Install dependencies
38
+ run: pip install -r requirements.txt pytest pytest-cov
39
+ - name: Run tests with coverage
40
+ run: |
41
+ PYTHONPATH=. python -m pytest tests/ -v \
42
+ --cov=codelens_env \
43
+ --cov=app \
44
+ --cov-report=xml \
45
+ --cov-report=term-missing \
46
+ --tb=short
47
+ env:
48
+ APP_ENV: test
49
+ TESTING: true
50
+ - name: Upload coverage report
51
+ uses: codecov/codecov-action@v4
52
+ if: matrix.python-version == '3.11'
53
+ with:
54
+ file: ./coverage.xml
55
+ fail_ci_if_error: false
56
+
57
+ # ── Job 3: Validate environment ────────────────────────────────
58
+ validate:
59
+ name: Validate All Scenarios
60
+ runs-on: ubuntu-latest
61
+ needs: test
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - uses: actions/setup-python@v5
65
+ with:
66
+ python-version: "3.11"
67
+ cache: pip
68
+ - run: pip install -r requirements.txt
69
+ - name: Validate all scenarios reachable
70
+ run: PYTHONPATH=. python scripts/validate.py
71
+
72
+ # ── Job 4: Docker build ────────────────────────────────────────
73
+ docker-build:
74
+ name: Docker Build
75
+ runs-on: ubuntu-latest
76
+ needs: test
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - uses: docker/setup-buildx-action@v3
80
+ - name: Build Docker image
81
+ uses: docker/build-push-action@v5
82
+ with:
83
+ context: .
84
+ target: production
85
+ push: false
86
+ load: true
87
+ tags: codelens-env:ci-${{ github.sha }}
88
+ cache-from: type=gha
89
+ cache-to: type=gha,mode=max
90
+ - name: Test container health
91
+ run: |
92
+ docker run -d --name test-container -p 7860:7860 codelens-env:ci-${{ github.sha }}
93
+ # Poll the /health endpoint with retries
94
+ for i in {1..15}; do
95
+ if curl -s -f http://localhost:7860/health > /dev/null; then
96
+ echo "Health check passed!"
97
+ docker stop test-container
98
+ exit 0
99
+ fi
100
+ echo "Waiting for app to start... (retry $i/15)"
101
+ sleep 2
102
+ done
103
+ echo "Health check FAILED. Showing container logs:"
104
+ docker logs test-container
105
+ docker stop test-container
106
+ exit 1
107
+
108
+ # ── Job 5: Publish (on main push only) ────────────────────────
109
+ publish:
110
+ name: Publish to GHCR
111
+ runs-on: ubuntu-latest
112
+ needs: [test, docker-build]
113
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
114
+ permissions:
115
+ contents: read
116
+ packages: write
117
+ steps:
118
+ - uses: actions/checkout@v4
119
+ - uses: docker/setup-buildx-action@v3
120
+ - uses: docker/login-action@v3
121
+ with:
122
+ registry: ghcr.io
123
+ username: ${{ github.actor }}
124
+ password: ${{ secrets.GITHUB_TOKEN }}
125
+ - name: Build and push
126
+ uses: docker/build-push-action@v5
127
+ with:
128
+ context: .
129
+ target: production
130
+ push: true
131
+ tags: |
132
+ ghcr.io/${{ github.repository }}:latest
133
+ ghcr.io/${{ github.repository }}:${{ github.sha }}
134
+ cache-from: type=gha
135
+ cache-to: type=gha,mode=max
.github/workflows/pylint.yml DELETED
@@ -1,23 +0,0 @@
1
- name: Pylint
2
-
3
- on: [push]
4
-
5
- jobs:
6
- build:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- python-version: ["3.8", "3.9", "3.10"]
11
- steps:
12
- - uses: actions/checkout@v4
13
- - name: Set up Python ${{ matrix.python-version }}
14
- uses: actions/setup-python@v3
15
- with:
16
- python-version: ${{ matrix.python-version }}
17
- - name: Install dependencies
18
- run: |
19
- python -m pip install --upgrade pip
20
- pip install pylint
21
- - name: Analysing the code with pylint
22
- run: |
23
- pylint $(git ls-files '*.py')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
+
6
+ ## [1.0.0] - 2026-04-05
7
+
8
+ ### Added
9
+ - **Models**: Complete Pydantic v2 models (`TaskId`, `Action`, `Scenario`, `EpisodeResult`, etc.)
10
+ - **Scenarios**: 30 synthetic PR scenarios (10 per task) with realistic Python diffs
11
+ - **Env**: Full episode state machine with noise budget, reward calculation, and history tracking
12
+ - **Graders**:
13
+ - `bug_grader.py`: Coverage + precision + severity-weighted scoring
14
+ - `security_grader.py`: Severity-accuracy-weighted scoring (CRITICAL misclassification penalized)
15
+ - `arch_grader.py`: Binary issue detection + verdict scoring + detail quality bonus
16
+ - **Config**: Pydantic-settings config with all options documented in `.env.example`
17
+ - **Database**: SQLModel persistence (`EpisodeRecord`, `LeaderboardRecord`, helpers)
18
+ - **API Endpoints**:
19
+ - `GET /stats`: Aggregate metrics across all recorded episodes
20
+ - `GET /episodes/{id}/replay`: Full action-by-action replay for completed episodes
21
+ - `GET /episodes`: List active episodes with metadata
22
+ - `GET /dashboard`: Web dashboard (dark theme, live leaderboard, WebSocket event feed, stats cards)
23
+ - **Security**:
24
+ - Rate limiting via `slowapi`: 60 req/min per IP (configurable)
25
+ - API key authentication: optional, off by default, enabled via `API_KEY_ENABLED=true`
26
+ - Added `TrustedHostMiddleware` and `Security Headers` (XSS, Frame protection)
27
+ - **Episode Lifecycle**: Auto-cleanup of expired episodes every 5 minutes (default 1hr)
28
+ - **Leaderboard**: Paginated `/leaderboard?limit=N&offset=M&task_id=X`
29
+ - **Baseline Agent**: Full rewrite with argparse CLI, `KeywordAgent` (35 rules), `LLMAgent` (Claude)
30
+ - **Evaluation**: `scripts/evaluate.py` for batch evaluation of all 30 scenarios with summary report and progress bars
31
+ - **Testing**: 155+ parametrized tests with full coverage reporting.
32
+ - **Dockerization**: Multi-stage `builder` + `production` builds with non-root user security.
33
+ - **CI/CD**: Unified 5-job pipeline (`lint`, `test`, `validate`, `docker-build`, `publish` to GHCR).
34
+ - **Branding**: Full rebrand to **CodeLens.**, including signature iconography.
35
+
36
+ ### Fixed
37
+ - **CLI**: Port mismatch in `baseline.py` (8000 → 7860) and added `--url`, `--task`, `--seed` CLI flags.
38
+ - **Crash Fixes**: Leaderboard submit crash after list slicing (captured rank before slice).
39
+ - **WebSocket**: Disconnect now handled with typed `WebSocketDisconnect` and `clients.discard()`.
40
+ - **Metadata**: Incoherent weight structure in `codelens.yaml` replaced with named, accurate pairs.
41
+ - **Security**: Implemented `TrustedHostMiddleware` and hardened headers.
42
+
43
+ ## [0.1.0] - Initial Baseline Fork
44
+ - Initial FastAPI skeleton.
45
+ - In-memory episode storage.
46
+ - Basic Dockerfile and Pylint-only CI.
CONTRIBUTING.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to CodeLens
2
+
3
+ Welcome! We appreciate contributions of all kinds. Here's how to get started.
4
+
5
+ ---
6
+
7
+ ## 🏗️ Development Setup
8
+
9
+ To get started with local development:
10
+
11
+ 1. **Clone and Install**:
12
+ ```bash
13
+ git clone https://github.com/ArshVermaGit/open-ev-code-handler.git
14
+ cd open-ev-code-handler
15
+ python3 -m venv venv && source venv/bin/activate
16
+ pip install -r requirements.txt
17
+ ```
18
+
19
+ 2. **Initialize**:
20
+ ```bash
21
+ cp .env.example .env
22
+ python scripts/migrate.py init
23
+ ```
24
+
25
+ 3. **Run Tests**:
26
+ ```bash
27
+ PYTHONPATH=. pytest tests/ -v
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 📝 Adding a New Scenario
33
+
34
+ Scenarios live in `codelens_env/scenarios.py`. Each scenario needs:
35
+
36
+ **Step 1**: Choose a task type and next sequential hash (e.g., `bug_011`).
37
+
38
+ **Step 2**: Write a realistic unified diff. The diff must:
39
+ - Start with `--- a/filename` and `+++ b/filename`
40
+ - Include `@@ -N,M +N,M @@` hunk headers
41
+ - Show a few lines of context (unchanged lines)
42
+ - Include the problematic line prefixed with `+`
43
+
44
+ Example patch:
45
+ ```python
46
+ patch="""--- a/api/users.py
47
+ +++ b/api/users.py
48
+ @@ -10,6 +10,6 @@ def get_users(page, size):
49
+ offset = page * size
50
+ - return items[offset:offset + size]
51
+ + return items[offset:offset + size - 1]
52
+ """
53
+ ```
54
+
55
+ **Step 3**: Define at least one `GroundTruthIssue` with:
56
+ - `keywords`: 2+ specific terms an agent body must contain (case-insensitive)
57
+ - `line_number`: the line in the diff where the issue occurs (±3 tolerance for bugs/security, ±5 for arch)
58
+ - `severity`: appropriate level (`critical` only for RCE/auth bypass/data loss)
59
+
60
+ **Step 4**: Add to `ALL_SCENARIOS` list and verify:
61
+ ```bash
62
+ PYTHONPATH=. python -m pytest tests/test_scenarios.py -v
63
+ ```
64
+ All 30 (or more) scenarios must pass validation.
65
+
66
+ ---
67
+
68
+ ## 🚀 Pull Request Process
69
+
70
+ 1. Fork the repo and create a branch: `feat/my-feature`, `fix/my-bug`, `test/more-tests`
71
+ 2. Make your changes
72
+ 3. Run the full test suite: `PYTHONPATH=. python -m pytest tests/ -v`
73
+ 4. Run the linter: `pylint codelens_env/ app.py` (target score ≥ 7.0)
74
+ 5. Open a PR against `main` with a clear description
75
+
76
+ ---
77
+
78
+ ## 📄 Code Style
79
+
80
+ - **Type hints** on all public functions and methods
81
+ - **Docstrings** on all public classes and non-trivial functions
82
+ - **pylint score** ≥ 7.0
83
+ - **Line length** ≤ 100 characters
84
+ - No bare `except:` clauses — always specify the exception type
85
+
86
+ ---
87
+
88
+ ## 📝 Commit Message Format
89
+
90
+ We use [Conventional Commits](https://www.conventionalcommits.org/):
91
+
92
+ ```
93
+ feat: add rate limiting to /reset endpoint
94
+ fix: correct leaderboard rank calculation after slice
95
+ test: add parametrized tests for all 30 scenarios
96
+ docs: update README quick start commands
97
+ refactor: extract episode cleanup into separate module
98
+ chore: upgrade pydantic to 2.6.1
99
+ ```
DEPLOYMENT.md ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CodeLens. Deployment Guide (Production)
2
+
3
+ Follow this guide to deploy **CodeLens. v1.0.0** to the professional cloud. This configuration uses **Vercel** for the frontend, **Render** for the backend, and **Supabase/Neon** for the PostgreSQL database.
4
+
5
+ ---
6
+
7
+ ## 1. 🗄️ Setup the Database (PostgreSQL)
8
+
9
+ Since SQLite is disk-based and will be deleted at every restart on Render/Vercel, you **must** use a managed PostgreSQL service.
10
+
11
+ 1. **Go to [Supabase](https://supabase.com)** or [Neon](https://neon.tech).
12
+ 2. **Create a new Project** called "CodeLens".
13
+ 3. **Copy your Connection String** (it should look like `postgres://user:pass@host:5432/dbname`).
14
+ 4. **Important**: Keep this URL safe—it is your `DATABASE_URL`.
15
+
16
+ ---
17
+
18
+ ## 2. 🚀 Setup the Backend (Render)
19
+
20
+ Render will host your FastAPI API and your Dockerized environment.
21
+
22
+ 1. **Go to [Render Dashboard](https://dashboard.render.com)**.
23
+ 2. **New -> Web Service** and connect your GitHub repository.
24
+ 3. **Configure**:
25
+ - **Runtime**: `Docker`.
26
+ - **Environment Variables**:
27
+ - `DATABASE_URL`: (Paste your Supabase/Neon URL here).
28
+ - `API_KEY_ENABLED`: `true` (highly recommended for production).
29
+ - `API_KEY`: A strong secret password.
30
+ - `APP_ENV`: `production`.
31
+ 4. **Deploy**: Render will automatically build the `Dockerfile` in the root and start the service.
32
+ 5. **Identify**: Copy your Render URL (e.g., `https://codelens-api.onrender.com`).
33
+
34
+ ---
35
+
36
+ ## 3. 🎨 Setup the Frontend (Vercel)
37
+
38
+ Vercel will host your React/Vite dashboard.
39
+
40
+ 1. **Go to [Vercel](https://vercel.com)**.
41
+ 2. **Import** your `dashboard` folder (or the whole repository and set the root directory to `dashboard`).
42
+ 3. **Update `vercel.json`**:
43
+ - Open [`dashboard/vercel.json`](file:///Users/arshverma/GitHub/open-ev-code-handler/dashboard/vercel.json).
44
+ - Replace `https://YOUR_BACKEND_URL.render.com` with your **real** Render URL.
45
+ 4. **Deploy**: Vercel will build the React application and provide a global dashboard link.
46
+
47
+ ---
48
+
49
+ ## 4. 🤖 Running Remote Evaluations
50
+
51
+ Once deployed, you can run the benchmark script from your local machine (or any CI) against your **production** instance:
52
+
53
+ ```bash
54
+ python scripts/evaluate.py --url https://your-render-url.com --api-key YOUR_SECRET_KEY
55
+ ```
56
+
57
+ ---
58
+
59
+ > [!CAUTION]
60
+ > **Database Migrations**: When you first deploy to a new PostgreSQL instance, the tables will be empty. The first request to the API will automatically trigger `create_db_and_tables()` via the lifespan hook—no manual SQL is required.
61
+
62
+ > [!TIP]
63
+ > **Vercel Rewrites**: The `vercel.json` rewrite rule is what allows the frontend to talk to the backend without CORS issues. Ensure the URL is exactly correct.
Dockerfile CHANGED
@@ -1,20 +1,52 @@
1
- FROM python:3.11-slim
 
2
 
3
- WORKDIR /app
4
 
5
- RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
 
 
 
6
 
 
7
  COPY requirements.txt .
8
- RUN pip install --no-cache-dir -r requirements.txt
 
 
9
 
10
- COPY . .
 
11
 
12
- EXPOSE 7860
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- ENV PYTHONPATH=/app
15
- ENV PORT=7860
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
18
- CMD curl -f http://localhost:7860/health || exit 1
19
 
20
- CMD ["python", "app.py"]
 
1
+ # ── Stage 1: Builder ──────────────────────────────────────────
2
+ FROM python:3.11-slim AS builder
3
 
4
+ WORKDIR /build
5
 
6
+ # Install build dependencies
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ curl \
9
+ && rm -rf /var/lib/apt/lists/*
10
 
11
+ # Install Python dependencies into /build/venv
12
  COPY requirements.txt .
13
+ RUN python -m venv /build/venv \
14
+ && /build/venv/bin/pip install --upgrade pip \
15
+ && /build/venv/bin/pip install --no-cache-dir -r requirements.txt
16
 
17
+ # ── Stage 2: Production ───────────────────────────────────────
18
+ FROM python:3.11-slim AS production
19
 
20
+ # Security: run as non-root user
21
+ RUN useradd --create-home --shell /bin/bash appuser
22
+
23
+ WORKDIR /app
24
+
25
+ # Install runtime system dependencies only
26
+ RUN apt-get update && apt-get install -y --no-install-recommends \
27
+ curl \
28
+ && rm -rf /var/lib/apt/lists/*
29
+
30
+ # Copy virtualenv from builder
31
+ COPY --from=builder /build/venv /app/venv
32
 
33
+ # Copy application code
34
+ COPY --chown=appuser:appuser . .
35
+
36
+ # Create data directory for SQLite DB
37
+ RUN mkdir -p /app/data && chown appuser:appuser /app/data
38
+
39
+ # Switch to non-root user
40
+ USER appuser
41
+
42
+ # Use venv python
43
+ ENV PATH="/app/venv/bin:$PATH"
44
+ ENV PYTHONPATH="/app"
45
+ ENV APP_PORT=7860
46
+
47
+ EXPOSE 7860
48
 
49
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
50
+ CMD curl -f http://localhost:7860/health || exit 1
51
 
52
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
GET_STARTED.md CHANGED
@@ -5,6 +5,7 @@ Welcome to **CodeLens.**, a production-grade AI agent evaluation environment. Th
5
  ---
6
 
7
  ## 1. Setup your Environment
 
8
  First, create a virtual environment and install the required Python dependencies.
9
 
10
  ```bash
@@ -18,7 +19,8 @@ pip install -r requirements.txt
18
  ---
19
 
20
  ## 2. Initialize the Database
21
- CodeLens uses SQLite for persistence. You must initialize the database before running the server for the first time.
 
22
 
23
  ```bash
24
  # Initialize the codelens.db with 30 baseline scenarios
@@ -28,6 +30,7 @@ python scripts/migrate.py init
28
  ---
29
 
30
  ## 3. Launch the System
 
31
  Start the FastAPI server. This serves both the **Agent API** and the **Interactive Dashboard**.
32
 
33
  ```bash
@@ -38,15 +41,17 @@ PYTHONPATH=. python app.py
38
  ---
39
 
40
  ## 4. Open the Dashboard
 
41
  Once the server is running, you can access the CodeLens Dashboard at:
42
 
43
  👉 **[http://localhost:7860/dashboard](http://localhost:7860/dashboard)**
44
 
45
- From here, you can see the top-10 leaderboard and monitor live agent evaluations.
46
 
47
  ---
48
 
49
  ## 5. Run your First Evaluation
 
50
  While keeping the server running in one terminal, open a **new terminal** and run the built-in Keyword agent to see results populated on the dashboard.
51
 
52
  ```bash
@@ -60,6 +65,7 @@ python scripts/evaluate.py --agent keyword
60
  ---
61
 
62
  ## 🧪 Running Tests
 
63
  To verify everything is working perfectly, you can run the full 155-test suite:
64
 
65
  ```bash
@@ -68,7 +74,7 @@ PYTHONPATH=. pytest tests/ -v
68
 
69
  ---
70
 
71
- ## \ud83d\udee0 Troubleshooting Common Errors
72
 
73
  ### 1. `ModuleNotFoundError: No module named 'requests'`
74
  This happens if you haven't activated the virtual environment in your current terminal tab.
@@ -80,7 +86,15 @@ The migration script requires an argument to proceed.
80
 
81
  ### 3. Logo not appearing in Dashboard
82
  If the logo shows a broken image placeholder:
83
- - **Fix**: Re-run the server with `PYTHONPATH=. python app.py`. The backend now has optimized routing to serve the `logo.svg`.
 
 
 
 
 
 
 
 
84
 
85
  ---
86
 
 
5
  ---
6
 
7
  ## 1. Setup your Environment
8
+
9
  First, create a virtual environment and install the required Python dependencies.
10
 
11
  ```bash
 
19
  ---
20
 
21
  ## 2. Initialize the Database
22
+
23
+ CodeLens uses SQLite for persistent episode and leaderboard data. You must initialize the database before running the server for the first time.
24
 
25
  ```bash
26
  # Initialize the codelens.db with 30 baseline scenarios
 
30
  ---
31
 
32
  ## 3. Launch the System
33
+
34
  Start the FastAPI server. This serves both the **Agent API** and the **Interactive Dashboard**.
35
 
36
  ```bash
 
41
  ---
42
 
43
  ## 4. Open the Dashboard
44
+
45
  Once the server is running, you can access the CodeLens Dashboard at:
46
 
47
  👉 **[http://localhost:7860/dashboard](http://localhost:7860/dashboard)**
48
 
49
+ From here, you can see the top-10 leaderboard and monitor real-time agent evaluations via the live event feed.
50
 
51
  ---
52
 
53
  ## 5. Run your First Evaluation
54
+
55
  While keeping the server running in one terminal, open a **new terminal** and run the built-in Keyword agent to see results populated on the dashboard.
56
 
57
  ```bash
 
65
  ---
66
 
67
  ## 🧪 Running Tests
68
+
69
  To verify everything is working perfectly, you can run the full 155-test suite:
70
 
71
  ```bash
 
74
 
75
  ---
76
 
77
+ ## 🛠️ Troubleshooting
78
 
79
  ### 1. `ModuleNotFoundError: No module named 'requests'`
80
  This happens if you haven't activated the virtual environment in your current terminal tab.
 
86
 
87
  ### 3. Logo not appearing in Dashboard
88
  If the logo shows a broken image placeholder:
89
+ - **Fix**: Re-run the server with `PYTHONPATH=. python app.py`. The backend has optimized routing to serve the brand iconography from the root.
90
+
91
+ ---
92
+
93
+ ## 🤝 Next Steps
94
+
95
+ - **Add Scenarios**: Learn how to author new code review benchmarks in **[CONTRIBUTING.md](CONTRIBUTING.md)**.
96
+ - **Batch Evaluation**: Scale up from single evaluations to full 30-scenario reports using `scripts/evaluate.py`.
97
+ - **Docker Deployment**: Deploy a production-ready container with `docker compose up`.
98
 
99
  ---
100
 
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Arsh Verma, Divyansh Rawat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -2,205 +2,220 @@
2
  <img src="assets/codelens-brand-v2.svg" width="400" alt="CodeLens." />
3
  </p>
4
 
5
- # CodeLens
6
 
7
- > **Can an AI agent catch the SQL injection that caused the $100M breach — before it ships?**
 
 
 
8
 
9
- This environment trains and evaluates agents on realistic Python code reviews grounded in real-world incident patterns. Unlike toy examples, every scenario is calibrated against actual production failure modes: payment mutations without idempotency keys, JWT verification bypassed for "dev convenience," pickle deserialization opening RCE vectors.
10
 
11
- [![CodeLens](https://img.shields.io/badge/CodeLens-1.0-blue)](https://huggingface.co/) [![Python 3.11](https://img.shields.io/badge/python-3.11-green)](https://python.org) [![FastAPI](https://img.shields.io/badge/FastAPI-0.109-red)](https://fastapi.tiangolo.com)
 
 
12
 
13
  ---
14
 
15
- ## Tasks
16
 
17
- | Task | Difficulty | Max Steps | Scenarios | Focus |
18
- |------|-----------|-----------|-----------|-------|
19
- | `bug_detection` | Easy | 10 | 10 | Off-by-one, race conditions, None deref, type mismatches |
20
- | `security_audit` | Medium | 15 | 10 | SQL injection, XSS, JWT bypass, pickle RCE, timing attacks |
21
- | `architectural_review` | Hard | 20 | 10 | N+1 queries, god objects, missing idempotency, SRP violations |
22
 
23
- ---
 
 
 
 
 
 
 
 
24
 
25
- ## Observation Space
26
-
27
- Each step the agent receives an `Observation` object:
28
-
29
- | Field | Type | Description |
30
- |-------|------|-------------|
31
- | `task_id` | `enum` | `bug_detection`, `security_audit`, or `architectural_review` |
32
- | `pr_title` | `str` | Pull request title (incident-inspired framing) |
33
- | `pr_description` | `str` | PR description from the author |
34
- | `diff` | `str` | Unified diff of the PR |
35
- | `files_changed` | `list[FileChange]` | Structured list of changed files |
36
- | `step_count` | `int` | Current step (0-indexed start after reset) |
37
- | `max_steps` | `int` | Maximum allowed steps for this task |
38
- | `history` | `list[ActionRecord]` | All actions taken so far this episode |
39
- | `noise_budget` | `int` | Remaining false-positive allowance (starts at 5) |
40
- | `service_name` | `str` | Name of the service being reviewed |
41
- | `service_criticality` | `"low"\|"medium"\|"high"\|"critical"` | How critical this service is to infrastructure |
42
- | `blast_radius` | `"low"\|"medium"\|"high"\|"critical"` | How many users/systems a bug here would affect |
43
- | `affected_users` | `int` | Estimated number of users impacted by a failure |
44
 
45
  ---
46
 
47
- ## Action Space
48
-
49
- The agent submits one action per step as a typed `Action` object:
50
 
51
- | `action_type` | Required Fields | Description |
52
- |--------------|----------------|-------------|
53
- | `flag_issue` | `body`, `filename`, `line_number`, `severity`, `category` | Flag a specific issue in the diff |
54
- | `approve` | `body`, `verdict="LGTM"` | Approve the PR — no issues or all caught |
55
- | `request_changes` | `body`, `verdict="REQUEST_CHANGES"` | Block merge — issues must be fixed |
56
- | `comment` | `body` | Leave a general comment (no reward signal) |
57
- | `ask_question` | `body` | Ask a clarifying question (no reward signal) |
58
 
59
- **Valid severities:** `low`, `medium`, `high`, `critical`
60
- **Valid categories:** `bug`, `security`, `architecture`, `performance`, `style`, `design`
 
 
 
61
 
62
  ---
63
 
64
- ## Reward Function
65
 
66
- Rewards are **incremental per step** (not end-of-episode):
67
 
68
- | Event | Reward Delta |
69
- |-------|-------------|
70
- | Correctly flag a ground-truth issue | `+0.1` to `+0.7` (depends on full grader recalculation) |
71
- | False positive flag | `-0.05` (consumes noise budget) |
72
- | Correct terminal verdict (approve/request_changes) | Final grader score delta |
73
- | Noise budget exhausted (5 FPs) | Episode terminates |
74
 
75
- **Grader formulas:**
76
- - **Bug:** `0.7 × recall + 0.3 × precision`
77
- - **Security:** `0.7 × severity_accuracy + 0.3 × keyword_overlap` (normalized by GT issues)
78
- - **Architecture:** `0.6 × issue_score + 0.2 × verdict_score + min(0.2, quality_bonus)`
79
 
80
- ---
 
81
 
82
- ## API Endpoints
83
 
84
- ```
85
- POST /reset → ResetResponse (episode_id + initial observation)
86
- POST /step/{episode_id} → StepResult (observation, reward, done, info)
87
- GET /state/{episode_id} → StateResult (step, score, issues_found, done)
88
- GET /result/{episode_id} → EpisodeResult (final_score, issues_found/missed)
89
- GET /health → {"status": "ok", ...}
90
- GET /leaderboard → top-10 per task
91
- POST /submit → submit agent score to leaderboard
92
- WS /ws/events → real-time step event stream
93
- ```
94
 
95
  ---
96
 
97
- ## Project Structure
98
 
99
- ```
100
- .
101
- ├── inference.py # Root inference script (CodeLens spec required)
102
- ├── app.py # FastAPI entry point
103
- ├── codelens.yaml # CodeLens spec manifest
104
- ├── Dockerfile # HuggingFace Spaces deployment
105
- ├── requirements.txt
106
- ├── codelens_env/
107
- │ ├── env.py # Episode state machine with incremental rewards
108
- │ ├── models.py # Pydantic models (Observation, Action, StateResult...)
109
- │ ├── scenario_bank.py # 30 scenarios with service metadata
110
- │ └── graders/
111
- │ ├── bug_grader.py # Recall × Precision scoring
112
- │ ├── security_grader.py # Severity accuracy + keyword overlap
113
- │ ├── arch_grader.py # Issue + verdict + quality scoring
114
- │ └── grader_utils.py # Line-number match + keyword overlap
115
- └── tests/
116
- ├── test_env.py # State machine + get_state() + reward tests
117
- └── test_graders.py # Grader unit tests
118
- ```
119
 
120
  ---
121
 
122
- ## \ud83d\ude80 Quick Start
 
 
123
 
124
- ### 1. Setup Environment
125
  ```bash
126
- python3 -m venv venv && source venv/bin/activate
127
- pip install -r requirements.txt
128
  ```
129
 
130
- ### 2. Initialize Database
 
131
  ```bash
132
- # This creates the codelens.db with all standard scenarios
133
- python scripts/migrate.py init
134
  ```
135
 
136
- ### 3. Launch CodeLens
 
137
  ```bash
138
- PYTHONPATH=. python app.py
139
- # API and Dashboard are now live at http://localhost:7860/dashboard
140
  ```
141
 
142
- ### 4. Run Evaluation (Baseline)
143
- In a new terminal:
 
 
 
 
144
  ```bash
145
- python scripts/evaluate.py --agent keyword
146
  ```
147
 
148
- ```bash
149
- export API_BASE_URL="https://api.openai.com/v1"
150
- export MODEL_NAME="gpt-4o"
151
- export HF_TOKEN="your-openai-key"
152
- export ENV_URL="http://localhost:7860"
153
 
154
- PYTHONPATH=. python inference.py
155
- ```
 
156
 
157
- Output format:
158
- ```
159
- [START] task=bug_detection env=http://localhost:7860 model=gpt-4o
160
- [STEP] step=1 action='flag_issue' reward=0.7000 done=False error=None
161
- [STEP] step=2 action='approve' reward=0.0000 done=True error=None
162
- [END] success=True steps=2 score=0.7000 rewards=[0.7, 0.0]
163
  ```
164
 
165
  ---
166
 
167
- ## Baseline Scores
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- *Run `python inference.py` after starting the server to reproduce.*
 
 
 
 
 
 
 
 
 
 
170
 
171
- | Task | Model | Avg Score | Success Rate |
172
- |------|-------|-----------|-------------|
173
- | `bug_detection` | gpt-3.5-turbo | ~0.52 | ~60% |
174
- | `security_audit` | gpt-3.5-turbo | ~0.38 | ~40% |
175
- | `architectural_review` | gpt-3.5-turbo | ~0.28 | ~30% |
176
- | `bug_detection` | gpt-4o | ~0.74 | ~80% |
177
- | `security_audit` | gpt-4o | ~0.61 | ~70% |
178
- | `architectural_review` | gpt-4o | ~0.45 | ~50% |
179
 
180
- > `architectural_review` is intentionally hard — frontier models score below 0.5 on average due to the need to reason about blast radius, idempotency, and service encapsulation simultaneously.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  ---
183
 
184
- ## Docker / HuggingFace Spaces
185
 
186
  ```bash
187
- docker build -t codelens-env .
188
- docker run -p 7860:7860 \
189
- -e PYTHONPATH=/app \
190
- codelens-env
 
 
 
 
 
 
 
 
191
  ```
192
 
193
- The server starts automatically via `python app.py`.
 
 
 
 
 
194
 
195
  ---
196
 
197
- ## Features
 
 
198
 
199
- - **30 Realistic Scenarios** — Incident-inspired PR titles tied to real service names, affected user counts, and blast radius labels
200
- - **Deterministic Grading** — MoE-style confidence-weighted matching with explainable per-issue scoring rubrics
201
- - **Incremental Rewards** — Step-level reward signals (`+δ` per correct flag, `-0.05` per FP) enable proper RL training
202
- - **Noise Budget** — Penalizes false positives to prevent reward gaming; episode terminates at 5 FPs
203
- - **Blast Radius Context** — `affected_users`, `service_criticality`, `blast_radius` in every observation
204
- - **WebSocket Stream** — Real-time step event broadcasting on `/ws/events`
205
- - **Leaderboard** — In-memory top-10 tracking per task
206
- - **Full CodeLens Spec** — `/reset`, `/step`, `/state`, `/result` + `[START]`/`[STEP]`/`[END]` stdout format
 
2
  <img src="assets/codelens-brand-v2.svg" width="400" alt="CodeLens." />
3
  </p>
4
 
5
+ # CodeLens Environment
6
 
7
+ ![CI](https://github.com/ArshVermaGit/open-ev-code-handler/actions/workflows/ci.yml/badge.svg)
8
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
9
+ ![License](https://img.shields.io/badge/license-MIT-green)
10
+ ![Docker](https://img.shields.io/badge/docker-ghcr.io-blue)
11
 
12
+ > **AI evaluation environment for benchmarking code review agents on 30 synthetic pull requests.**
13
 
14
+ CodeLens is a high-fidelity evaluation environment where AI agents act as senior code reviewers. They analyze pull request diffs to identify bugs, security vulnerabilities, and architectural issues before providing a final verdict.
15
+
16
+ Designed for researchers and developers building the next generation of AI code assistants, CodeLens provides 30 realistic Python scenarios with ground-truth labels and deterministic, reproducible scoring.
17
 
18
  ---
19
 
20
+ ## 🚀 Quick Start
21
 
22
+ Get up and running locally in under 2 minutes:
 
 
 
 
23
 
24
+ ```bash
25
+ git clone https://github.com/ArshVermaGit/open-ev-code-handler.git
26
+ cd open-ev-code-handler
27
+ cp .env.example .env
28
+ python3 -m venv venv && source venv/bin/activate
29
+ pip install -r requirements.txt
30
+ python scripts/migrate.py init
31
+ PYTHONPATH=. python app.py
32
+ ```
33
 
34
+ - **Dashboard**: [http://localhost:7860/dashboard](http://localhost:7860/dashboard)
35
+ - **API Docs**: [http://localhost:7860/docs](http://localhost:7860/docs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  ---
38
 
39
+ ## 📋 Evaluation Tasks
 
 
40
 
41
+ CodeLens benchmarks agents across three critical engineering domains:
 
 
 
 
 
 
42
 
43
+ | Task | Scenarios | Max Steps | Focus Area |
44
+ | ---------------------- | --------- | --------- | -------------------------------------------------------------------------- |
45
+ | `bug_detection` | 10 | 10 | Off-by-one errors, null dereferences, race conditions, exception handling |
46
+ | `security_audit` | 10 | 15 | SQL injection, hardcoded secrets, path traversal, insecure deserialization |
47
+ | `architectural_review` | 10 | 20 | N+1 queries, god classes, blocking async calls, circular imports |
48
 
49
  ---
50
 
51
+ ## 📈 Scoring System
52
 
53
+ ### Bug Detection
54
 
55
+ Score = `0.4 × coverage + 0.6 × avg_issue_score − 0.1 × false_positive_rate`
56
+ Issues are scored on **keyword accuracy** (50%) and **severity matching** (50%).
 
 
 
 
57
 
58
+ ### Security Audit
 
 
 
59
 
60
+ Score = `avg(per_issue_score)` where each issue = `0.7 × severity_accuracy + 0.3 × keyword_coverage`.
61
+ Severity accuracy is distance-weighted: misclassifying a **CRITICAL** issue as **LOW** incurs a major penalty.
62
 
63
+ ### Architectural Review
64
 
65
+ Score = `0.6 × detection_rate + 0.2 × verdict_accuracy + 0.2 × detail_quality`.
66
+ Detail quality rewards technical explanations that provide actionable developer feedback.
67
+
68
+ ### 🛑 Noise Budget
69
+
70
+ Every episode permits **5 false positive credits**. Flagging non-existent code paths spends one credit. Reaching zero terminates the episode immediately to prevent agent hallucination loops.
 
 
 
 
71
 
72
  ---
73
 
74
+ ## 🔌 API Reference
75
 
76
+ | Method | Endpoint | Auth | Description |
77
+ | :----- | :---------------------- | :------- | :-------------------------------------------- |
78
+ | `POST` | `/reset` | Optional | Start a new evaluation episode |
79
+ | `POST` | `/step/{id}` | Optional | Submit a review action (flag_issue, approve) |
80
+ | `GET` | `/result/{id}` | Optional | Retrieve final scores and logs for an episode |
81
+ | `GET` | `/leaderboard` | None | Paginated performance rankings |
82
+ | `POST` | `/submit` | Optional | Persist an episode result to the leaderboard |
83
+ | `GET` | `/stats` | None | Aggregate statistics across all agents |
84
+ | `GET` | `/episodes/{id}/replay` | Optional | Full event-by-event history replay |
85
+ | `GET` | `/dashboard` | None | Interactive Real-time Dashboard |
86
+ | `GET` | `/health` | None | System status and health check |
87
+
88
+ Authentication is disabled by default. Set `API_KEY_ENABLED=true` in `.env` for production parity.
 
 
 
 
 
 
 
89
 
90
  ---
91
 
92
+ ## 🐳 Running with Docker
93
+
94
+ ### Production Mode
95
 
 
96
  ```bash
97
+ docker compose up -d
98
+ # View logs: docker compose logs -f
99
  ```
100
 
101
+ ### Direct Pull
102
+
103
  ```bash
104
+ docker run -p 7860:7860 ghcr.io/ArshVermaGit/open-ev-code-handler:latest
 
105
  ```
106
 
107
+ ### Automated Testing
108
+
109
  ```bash
110
+ docker compose -f docker-compose.test.yml up
 
111
  ```
112
 
113
+ ---
114
+
115
+ ## 🤖 Baseline Agent & Evaluation
116
+
117
+ ### Single Scenario Trial
118
+
119
  ```bash
120
+ python scripts/baseline.py --task bug_detection --seed 3 --verbose
121
  ```
122
 
123
+ ### Full Benchmark (All 30 Scenarios)
 
 
 
 
124
 
125
+ ```bash
126
+ # Keyword-based baseline
127
+ python scripts/evaluate.py --agent keyword --output results.json
128
 
129
+ # LLM-powered reviewer (e.g. Claude)
130
+ python scripts/evaluate.py --agent llm --api-key $ANTHROPIC_API_KEY
 
 
 
 
131
  ```
132
 
133
  ---
134
 
135
+ ## 🧠 Writing Your Own Agent
136
+
137
+ CodeLens is designed to be agent-agnostic. Use standard HTTP requests to build your reviewer:
138
+
139
+ ```python
140
+ import requests
141
+
142
+ API = "http://localhost:7860"
143
+
144
+ # Start new episode
145
+ resp = requests.post(f"{API}/reset", json={"task_id": "bug_detection", "seed": 0})
146
+ episode_id = resp.json()["episode_id"]
147
 
148
+ done = False
149
+ while not done:
150
+ # Your agent logic analyzes the diff
151
+ action = {
152
+ "action_type": "flag_issue",
153
+ "body": "Identified a vulnerability line 14",
154
+ "filename": "api/search.py",
155
+ "line_number": 14,
156
+ "severity": "critical",
157
+ "category": "security"
158
+ }
159
 
160
+ result = requests.post(f"{API}/step/{episode_id}", json=action).json()
161
+ done = result["done"]
 
 
 
 
 
 
162
 
163
+ # Get final results
164
+ final = requests.get(f"{API}/result/{episode_id}").json()
165
+ print(f"Final Score: {final['final_score']}")
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 📂 Project Structure
171
+
172
+ ```text
173
+ open-ev-code-handler/
174
+ ├── app.py # FastAPI application (9 endpoints)
175
+ ├── codelens_env/ # Core evaluation logic
176
+ │ ├── database.py # SQLModel persistence layer
177
+ │ ├── env.py # Episode state machine
178
+ │ ├── models.py # Pydantic v2 data models
179
+ │ ├── scenarios.py # 30 Synthetic PR scenarios
180
+ │ └── graders/ # Grader implementations (Bug, Sec, Arch)
181
+ ├── scripts/ # CLI tools (baseline, evaluate, migrate)
182
+ ├── static/ # Compiled dashboard assets
183
+ ├── tests/ # 155+ Parametrized tests
184
+ ├── Dockerfile # Multi-stage, non-root build
185
+ ├── docker-compose.yml # Production orchestration
186
+ └── codelens.yaml # CodeLens v2 specification
187
+ ```
188
 
189
  ---
190
 
191
+ ## 🛠️ Development
192
 
193
  ```bash
194
+ # Setup
195
+ python -m venv venv && source venv/bin/activate
196
+ pip install -r requirements.txt
197
+
198
+ # Automated Tests
199
+ PYTHONPATH=. pytest tests/ -v --cov=codelens_env
200
+
201
+ # Linter Check
202
+ pylint codelens_env/ app.py
203
+
204
+ # Scenario Sanity Check
205
+ PYTHONPATH=. python scripts/validate.py
206
  ```
207
 
208
+ ## 👥 Authors & Maintainers
209
+
210
+ CodeLens is authored and maintained by:
211
+
212
+ - **Arsh Verma** — [GitHub](https://github.com/ArshVermaGit)
213
+ - **Divyansh Rawat** — [GitHub](https://github.com/DsThakurRawat)
214
 
215
  ---
216
 
217
+ ## 📄 Contributing & License
218
+
219
+ Please see **[CONTRIBUTING.md](CONTRIBUTING.md)** for details on authoring new scenarios and submission standards.
220
 
221
+ This project is licensed under the **[MIT License](LICENSE)**.
 
 
 
 
 
 
 
app.py CHANGED
@@ -42,9 +42,13 @@ logger = logging.getLogger("codelens_env")
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
  # Startup
45
- create_db_and_tables()
 
 
 
 
 
46
  cleanup_task = asyncio.create_task(cleanup_expired_episodes())
47
- logger.info(f"CodeLens API started — DB at {settings.db_path}")
48
 
49
  yield
50
 
@@ -68,7 +72,20 @@ app = FastAPI(
68
  lifespan=lifespan,
69
  )
70
 
71
- # ── CORS Middleware ───────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  app.add_middleware(
73
  CORSMiddleware,
74
  allow_origins=["*"] if settings.app_env == "development" else [f"http://localhost:{settings.app_port}"],
@@ -77,7 +94,18 @@ app.add_middleware(
77
  allow_headers=["*"],
78
  )
79
 
80
- # ── Rate Limiting ─────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
81
  limiter = Limiter(key_func=get_remote_address, default_limits=[f"{settings.rate_limit_per_minute}/minute"])
82
  app.state.limiter = limiter
83
  app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
 
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
  # Startup
45
+ if not os.getenv("TESTING"):
46
+ create_db_and_tables()
47
+ logger.info(f"CodeLens API started — DB at {settings.db_path}")
48
+ else:
49
+ logger.info("CodeLens API running in TESTING mode — DB initialization skipped")
50
+
51
  cleanup_task = asyncio.create_task(cleanup_expired_episodes())
 
52
 
53
  yield
54
 
 
72
  lifespan=lifespan,
73
  )
74
 
75
+ # ── Security & Middleware ──────────────────────────────────────────────────
76
+ from fastapi.middleware.trustedhost import TrustedHostMiddleware
77
+ from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
78
+
79
+ # 1. Trusted Host (Prevent Host-header injection)
80
+ app.add_middleware(
81
+ TrustedHostMiddleware,
82
+ allowed_hosts=["*"] if settings.app_env in ("development", "test") else [f"localhost", "127.0.0.1", "*.github.io", "testserver"]
83
+ )
84
+
85
+ # 2. Proxy Headers (Support Docker/Reverse-proxy)
86
+ app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
87
+
88
+ # 3. CORS
89
  app.add_middleware(
90
  CORSMiddleware,
91
  allow_origins=["*"] if settings.app_env == "development" else [f"http://localhost:{settings.app_port}"],
 
94
  allow_headers=["*"],
95
  )
96
 
97
+ # 4. Security Headers Middleware
98
+ @app.middleware("http")
99
+ async def add_security_headers(request: Request, call_next):
100
+ response = await call_next(request)
101
+ response.headers["X-Content-Type-Options"] = "nosniff"
102
+ response.headers["X-Frame-Options"] = "DENY"
103
+ response.headers["X-XSS-Protection"] = "1; mode=block"
104
+ response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
105
+ response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ws: wss:;"
106
+ return response
107
+
108
+ # 5. Rate Limiting
109
  limiter = Limiter(key_func=get_remote_address, default_limits=[f"{settings.rate_limit_per_minute}/minute"])
110
  app.state.limiter = limiter
111
  app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
codelens.yaml CHANGED
@@ -1,54 +1,77 @@
1
- version: "1.0"
2
- name: "codelens-codelens"
 
3
  description: >
4
- AI Senior Code Reviewer evaluation environment — trains agents to detect bugs,
5
- security vulnerabilities, and architectural issues in realistic Python PRs.
6
- Grounded in real-world incident patterns (payment systems, auth layers, microservices).
 
7
  entry_point: "app:app"
8
- base_url: "http://localhost:7860"
9
- api_version: "v1"
10
-
11
- tags:
12
- - codelens
13
- - code-review
14
- - security
15
- - software-engineering
16
 
17
  tasks:
18
  - id: "bug_detection"
19
- difficulty: easy
20
  max_steps: 10
21
  scenarios: 10
22
- description: >
23
- Identify logical errors, off-by-one bugs, mutable default arguments,
24
- None dereferences, race conditions, and type mismatches in Python snippets.
25
- Agents must FLAG issues with correct category/severity and submit a final verdict.
26
 
27
  - id: "security_audit"
28
- difficulty: medium
29
  max_steps: 15
30
  scenarios: 10
31
- description: >
32
- Detect OWASP Top-10 vulnerabilities: SQL injection, XSS, hardcoded secrets,
33
- JWT bypass, insecure deserialization (pickle RCE), path traversal, timing attacks,
34
- CORS misconfiguration, and missing rate limits in a payment-adjacent Python codebase.
35
 
36
  - id: "architectural_review"
37
- difficulty: hard
38
  max_steps: 20
39
  scenarios: 10
40
- description: >
41
- Evaluate system design quality: SRP violations, direct DB access bypassing API layers,
42
- synchronous blocking calls in event loops, missing idempotency keys on payment mutations,
43
- N+1 query patterns, god object anti-patterns, and shared mutable state between microservices.
44
- Requires reasoning about blast radius and service criticality in addition to code issues.
 
 
 
 
 
 
45
 
46
  grading:
47
  type: "deterministic"
48
- issue_matching:
 
49
  coverage_weight: 0.4
50
- precision_weight: 0.6
51
- quality_scoring:
52
- severity_weight: 0.7
53
- keyword_weight: 0.3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
 
 
 
 
 
 
 
1
+ version: "1.0.0"
2
+ name: "agentorg-codereview"
3
+ owners: ["Arsh Verma", "Divyansh Rawat"]
4
  description: >
5
+ AI Senior Code Reviewer evaluation environment for CodeLens.
6
+ Benchmarks agents on 30 synthetic pull requests across Bug Detection,
7
+ Security Audit, and Architectural Review tasks.
8
+
9
  entry_point: "app:app"
10
+ dashboard: "/dashboard"
11
+ api_docs: "/docs"
 
 
 
 
 
 
12
 
13
  tasks:
14
  - id: "bug_detection"
15
+ description: "Identify logical errors and edge cases in Python code"
16
  max_steps: 10
17
  scenarios: 10
18
+ difficulty_distribution:
19
+ easy: 2
20
+ medium: 6
21
+ hard: 2
22
 
23
  - id: "security_audit"
24
+ description: "Detect OWASP Top 10 vulnerabilities in Python code"
25
  max_steps: 15
26
  scenarios: 10
27
+ difficulty_distribution:
28
+ easy: 1
29
+ medium: 7
30
+ hard: 2
31
 
32
  - id: "architectural_review"
33
+ description: "Evaluate design patterns, coupling, and system constraints"
34
  max_steps: 20
35
  scenarios: 10
36
+ difficulty_distribution:
37
+ easy: 0
38
+ medium: 7
39
+ hard: 3
40
+
41
+ environment:
42
+ noise_budget: 5
43
+ line_tolerance_bug: 3
44
+ line_tolerance_arch: 5
45
+ keyword_match: "any" # agent body must contain ANY listed keyword
46
+ case_sensitive: false
47
 
48
  grading:
49
  type: "deterministic"
50
+
51
+ bug_detection:
52
  coverage_weight: 0.4
53
+ avg_issue_score_weight: 0.6
54
+ issue_score:
55
+ keyword_weight: 0.5
56
+ severity_weight: 0.5
57
+ false_positive_penalty: 0.1
58
+
59
+ security_audit:
60
+ formula: "avg_issue_score"
61
+ issue_score:
62
+ severity_weight: 0.7
63
+ keyword_weight: 0.3
64
+ severity_scale:
65
+ critical: 4
66
+ high: 3
67
+ medium: 2
68
+ low: 1
69
+ info: 0
70
+ severity_penalty_per_level: 0.3
71
 
72
+ architectural_review:
73
+ issue_detection_weight: 0.6
74
+ verdict_weight: 0.2
75
+ quality_weight: 0.2
76
+ quality_min_body_length: 20
77
+ quality_max_body_length: 200
codelens_env/config.py CHANGED
@@ -1,4 +1,5 @@
1
  from functools import lru_cache
 
2
  from pydantic_settings import BaseSettings, SettingsConfigDict
3
 
4
  class Settings(BaseSettings):
@@ -19,6 +20,7 @@ class Settings(BaseSettings):
19
  rate_limit_per_minute: int = 60 # requests per minute per IP
20
 
21
  # Persistence
 
22
  db_path: str = "./data/codelens.db"
23
  db_echo: bool = False # Set True to log all SQL queries
24
 
 
1
  from functools import lru_cache
2
+ from typing import Optional
3
  from pydantic_settings import BaseSettings, SettingsConfigDict
4
 
5
  class Settings(BaseSettings):
 
20
  rate_limit_per_minute: int = 60 # requests per minute per IP
21
 
22
  # Persistence
23
+ database_url: Optional[str] = None
24
  db_path: str = "./data/codelens.db"
25
  db_echo: bool = False # Set True to log all SQL queries
26
 
codelens_env/database.py CHANGED
@@ -7,6 +7,20 @@ from codelens_env.models import EpisodeResult, TaskId
7
 
8
  def get_engine():
9
  settings = get_settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  Path(settings.db_path).parent.mkdir(parents=True, exist_ok=True)
11
  return create_engine(
12
  f"sqlite:///{settings.db_path}",
 
7
 
8
  def get_engine():
9
  settings = get_settings()
10
+
11
+ if settings.database_url:
12
+ # Support Render/Heroku 'postgres://' URLs by converting to 'postgresql://'
13
+ url = settings.database_url
14
+ if url.startswith("postgres://"):
15
+ url = url.replace("postgres://", "postgresql://", 1)
16
+
17
+ return create_engine(
18
+ url,
19
+ echo=settings.db_echo,
20
+ pool_pre_ping=True, # Ensure connections are alive
21
+ )
22
+
23
+ # Fallback to local SQLite
24
  Path(settings.db_path).parent.mkdir(parents=True, exist_ok=True)
25
  return create_engine(
26
  f"sqlite:///{settings.db_path}",
dashboard/package.json CHANGED
@@ -1,7 +1,9 @@
1
  {
 
2
  "name": "codelens-dashboard",
 
3
  "private": true,
4
- "version": "0.1.0",
5
  "type": "module",
6
  "scripts": {
7
  "dev": "vite",
@@ -10,21 +12,21 @@
10
  },
11
  "dependencies": {
12
  "@fontsource/inter": "^5.1.0",
13
- "@tanstack/react-query": "^5.0.0",
14
- "clsx": "^2.1.0",
15
  "lucide-react": "^0.475.0",
16
- "react": "^18.3.0",
17
- "react-dom": "^18.3.0",
18
- "zustand": "^5.0.0"
19
  },
20
  "devDependencies": {
21
- "@tailwindcss/vite": "^4.1.0",
22
- "@types/node": "^25.5.2",
23
- "@types/react": "^18.3.0",
24
- "@types/react-dom": "^18.3.0",
25
- "@vitejs/plugin-react": "^4.3.0",
26
- "tailwindcss": "^4.1.0",
27
  "typescript": "^5.7.0",
28
- "vite": "^6.2.0"
29
  }
30
  }
 
1
  {
2
+ "$schema": "https://json.schemastore.org/package",
3
  "name": "codelens-dashboard",
4
+ "version": "1.0.0",
5
  "private": true,
6
+ "author": "Arsh Verma, Divyansh Rawat",
7
  "type": "module",
8
  "scripts": {
9
  "dev": "vite",
 
12
  },
13
  "dependencies": {
14
  "@fontsource/inter": "^5.1.0",
15
+ "@tanstack/react-query": "^5.62.0",
16
+ "clsx": "^2.1.1",
17
  "lucide-react": "^0.475.0",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0",
20
+ "zustand": "^5.0.3"
21
  },
22
  "devDependencies": {
23
+ "@tailwindcss/vite": "^4.0.0",
24
+ "@types/node": "^22.10.0",
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0",
27
+ "@vitejs/plugin-react": "^4.3.4",
28
+ "tailwindcss": "^4.0.0",
29
  "typescript": "^5.7.0",
30
+ "vite": "^6.0.0"
31
  }
32
  }
dashboard/tsconfig.json CHANGED
@@ -1,16 +1,28 @@
1
  {
 
2
  "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
 
 
 
5
  "module": "ESNext",
 
 
 
6
  "moduleResolution": "bundler",
 
 
 
 
 
7
  "jsx": "react-jsx",
 
 
8
  "strict": true,
9
  "noUnusedLocals": true,
10
  "noUnusedParameters": true,
11
- "noFallthroughCasesInSwitch": true,
12
- "skipLibCheck": true,
13
- "types": ["vite/client", "node"]
14
  },
15
  "include": ["src"]
16
  }
 
1
  {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
  "compilerOptions": {
4
+ "composite": true,
5
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
6
+ "target": "ES2020",
7
+ "useDefineForClassFields": true,
8
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
9
  "module": "ESNext",
10
+ "skipLibCheck": true,
11
+
12
+ /* Bundler mode */
13
  "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true,
17
+ "moduleDetection": "force",
18
+ "noEmit": true,
19
  "jsx": "react-jsx",
20
+
21
+ /* Linting */
22
  "strict": true,
23
  "noUnusedLocals": true,
24
  "noUnusedParameters": true,
25
+ "noFallthroughCasesInSwitch": true
 
 
26
  },
27
  "include": ["src"]
28
  }
dashboard/vercel.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "rewrites": [
3
+ {
4
+ "source": "/api/(.*)",
5
+ "destination": "https://YOUR_BACKEND_URL.render.com/$1"
6
+ },
7
+ {
8
+ "source": "/ws/(.*)",
9
+ "destination": "wss://YOUR_BACKEND_URL.render.com/ws/$1"
10
+ }
11
+ ],
12
+ "framework": "vite",
13
+ "buildCommand": "npm run build",
14
+ "outputDirectory": "dist"
15
+ }
docker-compose.test.yml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.9"
2
+
3
+ services:
4
+ test:
5
+ build:
6
+ context: .
7
+ target: builder
8
+ container_name: codelens-test
9
+ command: >
10
+ sh -c "/build/venv/bin/pip install pytest pytest-cov &&
11
+ PYTHONPATH=/app /build/venv/bin/python -m pytest tests/ -v --tb=short"
12
+ volumes:
13
+ - .:/app
14
+ environment:
15
+ - APP_ENV=test
docker-compose.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.9"
2
+
3
+ services:
4
+ api:
5
+ build:
6
+ context: .
7
+ target: production
8
+ container_name: codelens-api
9
+ ports:
10
+ - "${APP_PORT:-7860}:7860"
11
+ env_file:
12
+ - .env
13
+ environment:
14
+ - APP_ENV=${APP_ENV:-development}
15
+ - LOG_LEVEL=${LOG_LEVEL:-INFO}
16
+ volumes:
17
+ - codelens-data:/app/data
18
+ restart: unless-stopped
19
+ healthcheck:
20
+ test: ["CMD", "curl", "-f", "http://localhost:7860/health"]
21
+ interval: 30s
22
+ timeout: 10s
23
+ retries: 3
24
+ start_period: 15s
25
+
26
+ volumes:
27
+ codelens-data:
28
+ driver: local
requirements.txt CHANGED
@@ -1,15 +1,16 @@
1
- fastapi>=0.109.0
2
- uvicorn>=0.27.0
3
- pydantic>=2.6.1
4
  pytest>=8.0.0
5
- requests>=2.31.0
6
- websockets>=12.0
7
- httpx<0.28.0
8
- openai>=1.0.0
9
- pydantic-settings==2.2.1
10
- slowapi==0.1.9
11
- python-dotenv==1.0.1
12
- sqlmodel==0.0.16
13
- aiosqlite==0.20.0
14
- pytest-cov==4.1.0
15
- aiofiles==23.2.1
 
 
1
+ fastapi>=0.115.0
2
+ uvicorn>=0.30.0
3
+ pydantic>=2.10.0
4
  pytest>=8.0.0
5
+ requests>=2.32.0
6
+ websockets>=13.0
7
+ httpx>=0.28.0
8
+ openai>=1.50.0
9
+ pydantic-settings>=2.6.0
10
+ slowapi>=0.1.9
11
+ python-dotenv>=1.0.1
12
+ sqlmodel>=0.0.22
13
+ aiosqlite>=0.20.0
14
+ pytest-cov>=5.0.0
15
+ aiofiles>=24.0.0
16
+ PyYAML>=6.0.1
results.json CHANGED
@@ -1,6 +1,6 @@
1
  [
2
  {
3
- "episode_id": "0d8d0a22-2a81-48a1-8dbe-c9662da7dc2b",
4
  "task_id": "bug_detection",
5
  "seed": 0,
6
  "final_score": 0.0,
@@ -9,10 +9,10 @@
9
  "issues_total": 1,
10
  "noise_penalties": 0,
11
  "terminated_reason": "terminal_action",
12
- "duration_seconds": 0.03
13
  },
14
  {
15
- "episode_id": "6ddc5a56-86c1-43f2-8839-b6ad9eac2ad9",
16
  "task_id": "bug_detection",
17
  "seed": 1,
18
  "final_score": 0.0,
@@ -24,7 +24,7 @@
24
  "duration_seconds": 0.02
25
  },
26
  {
27
- "episode_id": "df00496e-0e19-4049-82d9-6a0f2a1e8f2a",
28
  "task_id": "bug_detection",
29
  "seed": 2,
30
  "final_score": 0.9167,
@@ -33,10 +33,10 @@
33
  "issues_total": 1,
34
  "noise_penalties": 5,
35
  "terminated_reason": "noise_exhausted",
36
- "duration_seconds": 0.06
37
  },
38
  {
39
- "episode_id": "26837a97-d3ad-4fd7-8e74-d0d713b9b137",
40
  "task_id": "bug_detection",
41
  "seed": 3,
42
  "final_score": 0.9167,
@@ -45,10 +45,10 @@
45
  "issues_total": 1,
46
  "noise_penalties": 5,
47
  "terminated_reason": "noise_exhausted",
48
- "duration_seconds": 0.04
49
  },
50
  {
51
- "episode_id": "41b3e01d-5498-42a1-a3f9-44bb87f542b6",
52
  "task_id": "bug_detection",
53
  "seed": 4,
54
  "final_score": 0.8267,
@@ -60,7 +60,7 @@
60
  "duration_seconds": 0.03
61
  },
62
  {
63
- "episode_id": "8331b8bf-f397-49be-bc07-62bb0bdddd5c",
64
  "task_id": "bug_detection",
65
  "seed": 5,
66
  "final_score": 0.0,
@@ -69,10 +69,10 @@
69
  "issues_total": 1,
70
  "noise_penalties": 0,
71
  "terminated_reason": "terminal_action",
72
- "duration_seconds": 0.02
73
  },
74
  {
75
- "episode_id": "22341ab4-ea27-416b-ba40-53f93b6090c9",
76
  "task_id": "bug_detection",
77
  "seed": 6,
78
  "final_score": 0.0,
@@ -84,7 +84,7 @@
84
  "duration_seconds": 0.02
85
  },
86
  {
87
- "episode_id": "9fc11809-6ba4-4bf6-b00e-9b8ffc61218f",
88
  "task_id": "bug_detection",
89
  "seed": 7,
90
  "final_score": 0.0,
@@ -96,7 +96,7 @@
96
  "duration_seconds": 0.02
97
  },
98
  {
99
- "episode_id": "dce54792-7fba-4e8c-8feb-404aa655172f",
100
  "task_id": "bug_detection",
101
  "seed": 8,
102
  "final_score": 0.9167,
@@ -105,10 +105,10 @@
105
  "issues_total": 1,
106
  "noise_penalties": 5,
107
  "terminated_reason": "noise_exhausted",
108
- "duration_seconds": 0.04
109
  },
110
  {
111
- "episode_id": "be559f48-b216-49b9-8f3d-ad9b50da3751",
112
  "task_id": "bug_detection",
113
  "seed": 9,
114
  "final_score": 0.0,
@@ -120,7 +120,7 @@
120
  "duration_seconds": 0.03
121
  },
122
  {
123
- "episode_id": "b2029cb1-c72e-438a-bd96-98a0aa9bc141",
124
  "task_id": "security_audit",
125
  "seed": 0,
126
  "final_score": 0.0,
@@ -132,7 +132,7 @@
132
  "duration_seconds": 0.03
133
  },
134
  {
135
- "episode_id": "e383f50e-5797-40d9-9f54-aa4142247a6e",
136
  "task_id": "security_audit",
137
  "seed": 1,
138
  "final_score": 1.0,
@@ -141,10 +141,10 @@
141
  "issues_total": 1,
142
  "noise_penalties": 5,
143
  "terminated_reason": "noise_exhausted",
144
- "duration_seconds": 0.04
145
  },
146
  {
147
- "episode_id": "6560439d-9050-4c94-96b1-c31111081c2e",
148
  "task_id": "security_audit",
149
  "seed": 2,
150
  "final_score": 0.0,
@@ -156,7 +156,7 @@
156
  "duration_seconds": 0.03
157
  },
158
  {
159
- "episode_id": "d9a9d12d-384c-4dd2-86cb-b55ea7604e60",
160
  "task_id": "security_audit",
161
  "seed": 3,
162
  "final_score": 0.85,
@@ -165,10 +165,10 @@
165
  "issues_total": 1,
166
  "noise_penalties": 5,
167
  "terminated_reason": "noise_exhausted",
168
- "duration_seconds": 0.04
169
  },
170
  {
171
- "episode_id": "463b8d96-ad63-472f-ad93-f20c99afe96e",
172
  "task_id": "security_audit",
173
  "seed": 4,
174
  "final_score": 0.0,
@@ -180,7 +180,7 @@
180
  "duration_seconds": 0.03
181
  },
182
  {
183
- "episode_id": "15e9c92a-559a-4ba6-b75b-b12ac6055f91",
184
  "task_id": "security_audit",
185
  "seed": 5,
186
  "final_score": 0.0,
@@ -189,10 +189,10 @@
189
  "issues_total": 1,
190
  "noise_penalties": 5,
191
  "terminated_reason": "noise_exhausted",
192
- "duration_seconds": 0.06
193
  },
194
  {
195
- "episode_id": "38bd8513-7511-4a2f-8642-fc23f50b61bd",
196
  "task_id": "security_audit",
197
  "seed": 6,
198
  "final_score": 0.0,
@@ -204,7 +204,7 @@
204
  "duration_seconds": 0.03
205
  },
206
  {
207
- "episode_id": "8a6e48fd-1b17-475f-b495-a1cd8ce2e70c",
208
  "task_id": "security_audit",
209
  "seed": 7,
210
  "final_score": 0.0,
@@ -216,7 +216,7 @@
216
  "duration_seconds": 0.03
217
  },
218
  {
219
- "episode_id": "3ec40833-ce55-4a63-ad90-55b2e35370d1",
220
  "task_id": "security_audit",
221
  "seed": 8,
222
  "final_score": 0.0,
@@ -225,10 +225,10 @@
225
  "issues_total": 1,
226
  "noise_penalties": 5,
227
  "terminated_reason": "noise_exhausted",
228
- "duration_seconds": 0.03
229
  },
230
  {
231
- "episode_id": "87fa57a3-27f3-4f9b-a0ad-008ff9b4c7f2",
232
  "task_id": "security_audit",
233
  "seed": 9,
234
  "final_score": 0.0,
@@ -240,7 +240,7 @@
240
  "duration_seconds": 0.03
241
  },
242
  {
243
- "episode_id": "8a878e69-51c2-4992-8276-616cbc798efd",
244
  "task_id": "architectural_review",
245
  "seed": 0,
246
  "final_score": 0.0,
@@ -249,10 +249,10 @@
249
  "issues_total": 1,
250
  "noise_penalties": 0,
251
  "terminated_reason": "terminal_action",
252
- "duration_seconds": 0.02
253
  },
254
  {
255
- "episode_id": "c85766aa-d231-4998-8d95-b4dbbfd5aea6",
256
  "task_id": "architectural_review",
257
  "seed": 1,
258
  "final_score": 0.059,
@@ -261,10 +261,10 @@
261
  "issues_total": 1,
262
  "noise_penalties": 5,
263
  "terminated_reason": "noise_exhausted",
264
- "duration_seconds": 0.04
265
  },
266
  {
267
- "episode_id": "29122fdb-ed30-4a46-8b04-4aa8c2025e6c",
268
  "task_id": "architectural_review",
269
  "seed": 2,
270
  "final_score": 0.661,
@@ -273,10 +273,10 @@
273
  "issues_total": 1,
274
  "noise_penalties": 5,
275
  "terminated_reason": "noise_exhausted",
276
- "duration_seconds": 0.04
277
  },
278
  {
279
- "episode_id": "1388c816-8e96-4014-b255-81382e9353ba",
280
  "task_id": "architectural_review",
281
  "seed": 3,
282
  "final_score": 0.658,
@@ -288,7 +288,7 @@
288
  "duration_seconds": 0.03
289
  },
290
  {
291
- "episode_id": "adee24d2-cb77-48b6-b00b-72c39d7d7c7e",
292
  "task_id": "architectural_review",
293
  "seed": 4,
294
  "final_score": 0.058,
@@ -300,7 +300,7 @@
300
  "duration_seconds": 0.03
301
  },
302
  {
303
- "episode_id": "12508a15-eb46-4628-b750-cde1b5f91828",
304
  "task_id": "architectural_review",
305
  "seed": 5,
306
  "final_score": 0.657,
@@ -309,10 +309,10 @@
309
  "issues_total": 1,
310
  "noise_penalties": 5,
311
  "terminated_reason": "noise_exhausted",
312
- "duration_seconds": 0.04
313
  },
314
  {
315
- "episode_id": "520fb8ad-58fe-48fa-8a96-89e552e12924",
316
  "task_id": "architectural_review",
317
  "seed": 6,
318
  "final_score": 0.059,
@@ -324,7 +324,7 @@
324
  "duration_seconds": 0.03
325
  },
326
  {
327
- "episode_id": "86982fa4-cee7-48fc-b8d1-347a2f650f6d",
328
  "task_id": "architectural_review",
329
  "seed": 7,
330
  "final_score": 0.664,
@@ -333,10 +333,10 @@
333
  "issues_total": 1,
334
  "noise_penalties": 5,
335
  "terminated_reason": "noise_exhausted",
336
- "duration_seconds": 0.04
337
  },
338
  {
339
- "episode_id": "086e541a-776e-4ac6-b44e-1eeb76d88b4e",
340
  "task_id": "architectural_review",
341
  "seed": 8,
342
  "final_score": 0.039,
@@ -345,10 +345,10 @@
345
  "issues_total": 1,
346
  "noise_penalties": 5,
347
  "terminated_reason": "noise_exhausted",
348
- "duration_seconds": 0.03
349
  },
350
  {
351
- "episode_id": "3bcd91ce-0d0d-48c5-ab25-bc8534906f5b",
352
  "task_id": "architectural_review",
353
  "seed": 9,
354
  "final_score": 0.075,
@@ -357,6 +357,6 @@
357
  "issues_total": 1,
358
  "noise_penalties": 5,
359
  "terminated_reason": "noise_exhausted",
360
- "duration_seconds": 0.03
361
  }
362
  ]
 
1
  [
2
  {
3
+ "episode_id": "27c65a00-9315-4d7d-b457-f8d64b0da466",
4
  "task_id": "bug_detection",
5
  "seed": 0,
6
  "final_score": 0.0,
 
9
  "issues_total": 1,
10
  "noise_penalties": 0,
11
  "terminated_reason": "terminal_action",
12
+ "duration_seconds": 0.02
13
  },
14
  {
15
+ "episode_id": "8bdcee56-8a56-4ec8-88fc-a70e78ab48f2",
16
  "task_id": "bug_detection",
17
  "seed": 1,
18
  "final_score": 0.0,
 
24
  "duration_seconds": 0.02
25
  },
26
  {
27
+ "episode_id": "d3dcda88-ce7e-4965-9409-4e97c98cf444",
28
  "task_id": "bug_detection",
29
  "seed": 2,
30
  "final_score": 0.9167,
 
33
  "issues_total": 1,
34
  "noise_penalties": 5,
35
  "terminated_reason": "noise_exhausted",
36
+ "duration_seconds": 0.04
37
  },
38
  {
39
+ "episode_id": "822dfb7d-d67b-4dc9-9cfe-866665a9e5b9",
40
  "task_id": "bug_detection",
41
  "seed": 3,
42
  "final_score": 0.9167,
 
45
  "issues_total": 1,
46
  "noise_penalties": 5,
47
  "terminated_reason": "noise_exhausted",
48
+ "duration_seconds": 0.03
49
  },
50
  {
51
+ "episode_id": "c35c2096-b7b8-4a54-b0c7-d8fa1e1538bf",
52
  "task_id": "bug_detection",
53
  "seed": 4,
54
  "final_score": 0.8267,
 
60
  "duration_seconds": 0.03
61
  },
62
  {
63
+ "episode_id": "9b4e5191-57da-4b10-b31e-5b8236b9b1f4",
64
  "task_id": "bug_detection",
65
  "seed": 5,
66
  "final_score": 0.0,
 
69
  "issues_total": 1,
70
  "noise_penalties": 0,
71
  "terminated_reason": "terminal_action",
72
+ "duration_seconds": 0.01
73
  },
74
  {
75
+ "episode_id": "c1e64a16-512a-4716-b0f9-5d9fe14a142d",
76
  "task_id": "bug_detection",
77
  "seed": 6,
78
  "final_score": 0.0,
 
84
  "duration_seconds": 0.02
85
  },
86
  {
87
+ "episode_id": "cc8bd066-506e-46de-ba0f-0a446f045945",
88
  "task_id": "bug_detection",
89
  "seed": 7,
90
  "final_score": 0.0,
 
96
  "duration_seconds": 0.02
97
  },
98
  {
99
+ "episode_id": "20dd7158-a49f-4b6c-adcd-6b76b4274e94",
100
  "task_id": "bug_detection",
101
  "seed": 8,
102
  "final_score": 0.9167,
 
105
  "issues_total": 1,
106
  "noise_penalties": 5,
107
  "terminated_reason": "noise_exhausted",
108
+ "duration_seconds": 0.03
109
  },
110
  {
111
+ "episode_id": "14b9d1a5-03b7-45b2-8b23-b6c387bced5b",
112
  "task_id": "bug_detection",
113
  "seed": 9,
114
  "final_score": 0.0,
 
120
  "duration_seconds": 0.03
121
  },
122
  {
123
+ "episode_id": "2b1c4b7f-cf05-4312-89d2-49883b9ed6c1",
124
  "task_id": "security_audit",
125
  "seed": 0,
126
  "final_score": 0.0,
 
132
  "duration_seconds": 0.03
133
  },
134
  {
135
+ "episode_id": "47145114-7a99-4c81-baa9-1f9c4221f48a",
136
  "task_id": "security_audit",
137
  "seed": 1,
138
  "final_score": 1.0,
 
141
  "issues_total": 1,
142
  "noise_penalties": 5,
143
  "terminated_reason": "noise_exhausted",
144
+ "duration_seconds": 0.03
145
  },
146
  {
147
+ "episode_id": "5d9d3f74-a127-46b2-a331-45f30f0d2f6f",
148
  "task_id": "security_audit",
149
  "seed": 2,
150
  "final_score": 0.0,
 
156
  "duration_seconds": 0.03
157
  },
158
  {
159
+ "episode_id": "f383afc4-df2d-4a60-abd1-a583cb2de538",
160
  "task_id": "security_audit",
161
  "seed": 3,
162
  "final_score": 0.85,
 
165
  "issues_total": 1,
166
  "noise_penalties": 5,
167
  "terminated_reason": "noise_exhausted",
168
+ "duration_seconds": 0.03
169
  },
170
  {
171
+ "episode_id": "df23e18b-c21a-4390-9afa-f77eb1b8050b",
172
  "task_id": "security_audit",
173
  "seed": 4,
174
  "final_score": 0.0,
 
180
  "duration_seconds": 0.03
181
  },
182
  {
183
+ "episode_id": "bcec83af-4aaf-4997-922a-1556ca63fcf3",
184
  "task_id": "security_audit",
185
  "seed": 5,
186
  "final_score": 0.0,
 
189
  "issues_total": 1,
190
  "noise_penalties": 5,
191
  "terminated_reason": "noise_exhausted",
192
+ "duration_seconds": 0.03
193
  },
194
  {
195
+ "episode_id": "65afedd0-9ad1-45f7-9615-dafeaa390338",
196
  "task_id": "security_audit",
197
  "seed": 6,
198
  "final_score": 0.0,
 
204
  "duration_seconds": 0.03
205
  },
206
  {
207
+ "episode_id": "0e8b65e5-d59c-41fb-8038-1f3b1d4b657b",
208
  "task_id": "security_audit",
209
  "seed": 7,
210
  "final_score": 0.0,
 
216
  "duration_seconds": 0.03
217
  },
218
  {
219
+ "episode_id": "ae477e21-2718-477e-8a11-e46bda49bea7",
220
  "task_id": "security_audit",
221
  "seed": 8,
222
  "final_score": 0.0,
 
225
  "issues_total": 1,
226
  "noise_penalties": 5,
227
  "terminated_reason": "noise_exhausted",
228
+ "duration_seconds": 0.04
229
  },
230
  {
231
+ "episode_id": "0cd3d18c-cdf6-4752-9dc8-cade848cee0e",
232
  "task_id": "security_audit",
233
  "seed": 9,
234
  "final_score": 0.0,
 
240
  "duration_seconds": 0.03
241
  },
242
  {
243
+ "episode_id": "d9f79316-57ea-42ad-b191-797ea895951b",
244
  "task_id": "architectural_review",
245
  "seed": 0,
246
  "final_score": 0.0,
 
249
  "issues_total": 1,
250
  "noise_penalties": 0,
251
  "terminated_reason": "terminal_action",
252
+ "duration_seconds": 0.01
253
  },
254
  {
255
+ "episode_id": "65112da3-d319-4eb3-9774-1c43313fe1ec",
256
  "task_id": "architectural_review",
257
  "seed": 1,
258
  "final_score": 0.059,
 
261
  "issues_total": 1,
262
  "noise_penalties": 5,
263
  "terminated_reason": "noise_exhausted",
264
+ "duration_seconds": 0.03
265
  },
266
  {
267
+ "episode_id": "c1d6bd63-1abf-403b-97be-cf1962959910",
268
  "task_id": "architectural_review",
269
  "seed": 2,
270
  "final_score": 0.661,
 
273
  "issues_total": 1,
274
  "noise_penalties": 5,
275
  "terminated_reason": "noise_exhausted",
276
+ "duration_seconds": 0.03
277
  },
278
  {
279
+ "episode_id": "fab56551-f2ae-42be-88eb-854ef236b29a",
280
  "task_id": "architectural_review",
281
  "seed": 3,
282
  "final_score": 0.658,
 
288
  "duration_seconds": 0.03
289
  },
290
  {
291
+ "episode_id": "8f4b8fa9-f281-4bb2-ae71-7ad6c7ca7fc9",
292
  "task_id": "architectural_review",
293
  "seed": 4,
294
  "final_score": 0.058,
 
300
  "duration_seconds": 0.03
301
  },
302
  {
303
+ "episode_id": "7eaa445d-774d-44b1-80d9-06e978b022df",
304
  "task_id": "architectural_review",
305
  "seed": 5,
306
  "final_score": 0.657,
 
309
  "issues_total": 1,
310
  "noise_penalties": 5,
311
  "terminated_reason": "noise_exhausted",
312
+ "duration_seconds": 0.03
313
  },
314
  {
315
+ "episode_id": "103d6e2b-e40d-49ff-8f36-6910d463b48b",
316
  "task_id": "architectural_review",
317
  "seed": 6,
318
  "final_score": 0.059,
 
324
  "duration_seconds": 0.03
325
  },
326
  {
327
+ "episode_id": "5206e9d8-0ee0-482c-ad3d-34adf2ce57a7",
328
  "task_id": "architectural_review",
329
  "seed": 7,
330
  "final_score": 0.664,
 
333
  "issues_total": 1,
334
  "noise_penalties": 5,
335
  "terminated_reason": "noise_exhausted",
336
+ "duration_seconds": 0.03
337
  },
338
  {
339
+ "episode_id": "c147abd7-f887-44a2-b166-5382e292d793",
340
  "task_id": "architectural_review",
341
  "seed": 8,
342
  "final_score": 0.039,
 
345
  "issues_total": 1,
346
  "noise_penalties": 5,
347
  "terminated_reason": "noise_exhausted",
348
+ "duration_seconds": 0.08
349
  },
350
  {
351
+ "episode_id": "6fe54153-a878-47d7-a521-5cd70f55f6b8",
352
  "task_id": "architectural_review",
353
  "seed": 9,
354
  "final_score": 0.075,
 
357
  "issues_total": 1,
358
  "noise_penalties": 5,
359
  "terminated_reason": "noise_exhausted",
360
+ "duration_seconds": 0.09
361
  }
362
  ]
scripts/reset_db.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Reset the CodeLens database: deletes the SQLite file and re-initializes tables.
4
+ Useful for clearing test data and starting fresh evaluation benchmarks.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Add project root to path
12
+ sys.path.insert(0, str(Path(__file__).parent.parent))
13
+
14
+ from codelens_env.config import get_settings
15
+ from codelens_env.database import create_db_and_tables
16
+
17
+ def reset_db():
18
+ settings = get_settings()
19
+ db_path = Path(settings.db_path)
20
+
21
+ # 1. Delete existing database file
22
+ if db_path.exists():
23
+ print(f"Removing existing database at: {db_path}")
24
+ try:
25
+ os.remove(db_path)
26
+ print("Successfully deleted old records.")
27
+ except Exception as e:
28
+ print(f"Error deleting file: {e}")
29
+ sys.exit(1)
30
+ else:
31
+ print(f"No existing database found at {db_path}")
32
+
33
+ # 2. Re-initialize
34
+ print(f"Re-initializing schema...")
35
+ try:
36
+ create_db_and_tables()
37
+ print("Database reset successfully. You now have a clean dashboard.")
38
+ except Exception as e:
39
+ print(f"Error re-initializing: {e}")
40
+ sys.exit(1)
41
+
42
+ if __name__ == "__main__":
43
+ confirm = input("This will permanently delete all leaderboard and episode data. Proceed? [y/N]: ")
44
+ if confirm.lower() == 'y':
45
+ reset_db()
46
+ else:
47
+ print("Reset aborted.")
tests/conftest.py CHANGED
@@ -1,4 +1,7 @@
1
  import pytest
 
 
 
2
  from fastapi.testclient import TestClient
3
  from sqlmodel import SQLModel, Session, create_engine
4
  from sqlmodel.pool import StaticPool
 
1
  import pytest
2
+ import os
3
+ os.environ["TESTING"] = "true"
4
+ os.environ["APP_ENV"] = "test"
5
  from fastapi.testclient import TestClient
6
  from sqlmodel import SQLModel, Session, create_engine
7
  from sqlmodel.pool import StaticPool
tests/test_api.py CHANGED
@@ -3,15 +3,13 @@ from fastapi.testclient import TestClient
3
  from app import app
4
  from codelens_env.models import TaskId, ActionType, Category, Severity, Verdict
5
 
6
- def test_api_health():
7
- client = TestClient(app)
8
  response = client.get("/health")
9
  assert response.status_code == 200
10
  assert response.json()["status"] == "ok"
11
  assert response.json()["env_ready"] is True
12
 
13
- def test_api_workflow():
14
- client = TestClient(app)
15
 
16
  # 1. Reset
17
  reset_resp = client.post("/reset", json={"task_id": "bug_detection", "seed": 1})
@@ -34,8 +32,7 @@ def test_api_workflow():
34
  assert result_resp.status_code == 200
35
  assert result_resp.json()["final_score"] >= 0
36
 
37
- def test_api_leaderboard():
38
- client = TestClient(app)
39
  # Submit a score
40
  sub = {
41
  "agent_name": "test_agent",
@@ -55,8 +52,7 @@ def test_api_leaderboard():
55
  assert len(bug_entries) > 0
56
  assert bug_entries[0]["agent_name"] == "test_agent"
57
 
58
- def test_api_invalid_episode():
59
- client = TestClient(app)
60
  response = client.post("/step/nonexistent-id", json={
61
  "action_type": "comment",
62
  "body": "hello"
 
3
  from app import app
4
  from codelens_env.models import TaskId, ActionType, Category, Severity, Verdict
5
 
6
+ def test_api_health(client):
 
7
  response = client.get("/health")
8
  assert response.status_code == 200
9
  assert response.json()["status"] == "ok"
10
  assert response.json()["env_ready"] is True
11
 
12
+ def test_api_workflow(client):
 
13
 
14
  # 1. Reset
15
  reset_resp = client.post("/reset", json={"task_id": "bug_detection", "seed": 1})
 
32
  assert result_resp.status_code == 200
33
  assert result_resp.json()["final_score"] >= 0
34
 
35
+ def test_api_leaderboard(client):
 
36
  # Submit a score
37
  sub = {
38
  "agent_name": "test_agent",
 
52
  assert len(bug_entries) > 0
53
  assert bug_entries[0]["agent_name"] == "test_agent"
54
 
55
+ def test_api_invalid_episode(client):
 
56
  response = client.post("/step/nonexistent-id", json={
57
  "action_type": "comment",
58
  "body": "hello"