Skip to content
Open
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
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
verify:
name: Verify Stability
Expand Down Expand Up @@ -46,10 +49,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20
cache: 'npm'
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 1'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write

strategy:
matrix:
language: ['javascript-typescript']

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Initialize CodeQL
uses: github/codeql-action/init@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3.36.3
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@411c4c9a36b3fca4d674f06b6396b2c6d23522c6 # v3.36.3
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Changelog

## v0.9.2

### What's New

- **UI Redesign:** Full visual overhaul of the dashboard, landing page, and auth flows with an updated color system, improved dark mode, and a cleaner layout overall.
- **Code Splitting:** All pages now use `React.lazy` for async loading, reducing initial bundle size and improving load times.
- **Custom LLM Providers:** Manage custom OpenAI-compatible API providers directly from the dashboard. Rate limits are optional. No more hardcoded keys in wrangler config.
- **Cloudflare Setup Script:** A new Node.js script automates the full Cloudflare deployment setup, making self-hosting significantly easier.
- **Increased Review Capacity:** Max files processed per review raised from 15 to 100.

### Improvements

- Review jobs are now resumable and lease-aware, stalled reviews can recover automatically.
- Added retry logic for transient model provider failures.
- Optimized job polling with ETag caching and adaptive delays.
- Improved settings UI, error reporting, and API robustness across the board.
- Added GitHub Actions CI with a disposable test environment for the full test suite.

### Bug Fixes

- Fixed `APP_PRIVATE_KEY` parsing for single-line strings with literal `\n` sequences.
- Fixed database migration failures on existing deployments.
- Fixed duplicate `/api/auth/updates-email` calls on page load.
- Fixed file review status bar rendering on the dashboard.

**Full Changelog**: https://github.com/devarshishimpi/codra/commits/v0.9.2
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ Codra listens to GitHub pull request events, runs AI-powered review jobs, posts
- Mention-triggered reviews for on-demand analysis
- Inline GitHub review comments plus summary reviews and check run updates
- Queue-backed processing through Cloudflare Queues
- Dead letter queue inspection, replay, and purge workflows
- GitHub OAuth dashboard authentication
- External PostgreSQL storage through Cloudflare Hyperdrive
- Dashboard-managed LLM providers for OpenAI, OpenRouter, Anthropic, Google, and Cloudflare models
Expand All @@ -66,7 +65,7 @@ Codra listens to GitHub pull request events, runs AI-powered review jobs, posts
- **Worker**: Cloudflare Workers, Hono, Wrangler
- **Dashboard**: React, Vite, Tailwind CSS, Radix UI, Recharts
- **Data**: PostgreSQL, Cloudflare Hyperdrive, Cloudflare KV
- **Queues**: Cloudflare Queues with DLQ workflows
- **Queues**: Cloudflare Queues and Workflows
- **Models**: OpenAI, OpenRouter, Anthropic, Google, and Cloudflare providers
- **GitHub**: GitHub App webhooks, checks, reviews, and OAuth
- **Quality**: TypeScript, Zod, Vitest, Playwright browser tests
Expand Down
4 changes: 4 additions & 0 deletions db/migrations/004_review_performance_settings.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INSERT INTO global_settings (key, value) VALUES
('review_concurrency_level', 'medium'),
('review_max_comments', '10')
ON CONFLICT (key) DO NOTHING;
3 changes: 3 additions & 0 deletions db/migrations/005_drop_model_rate_limits.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE model_configs DROP COLUMN IF EXISTS rpm;
ALTER TABLE model_configs DROP COLUMN IF EXISTS tpm;
ALTER TABLE model_configs DROP COLUMN IF EXISTS rpd;
5 changes: 5 additions & 0 deletions db/migrations/006_job_continuation_count.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Tracks how many times a job has rescheduled the *same* phase without completing any
-- file review (a "no-progress continuation"). Reset to 0 whenever a chunk makes progress.
-- A hard ceiling on this counter (see MAX_JOB_CONTINUATIONS in review.ts) stops a job that
-- can never make headway from churning indefinitely on transient/budget deferrals.
ALTER TABLE jobs ADD COLUMN IF NOT EXISTS continuation_count INT NOT NULL DEFAULT 0;
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"clsx": "^2.1.1",
"hono": "^4.12.12",
"jsonrepair": "^3.13.3",
"lenis": "^1.3.25",
"lucide-react": "^1.8.0",
"motion": "^12.42.2",
"picomatch": "^4.0.4",
Expand Down
31 changes: 8 additions & 23 deletions scripts/migrate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,6 @@ async function ensureModelCatalog() {
await query('ALTER TABLE model_configs ADD COLUMN IF NOT EXISTS provider_id UUID');
await query('ALTER TABLE model_configs ADD COLUMN IF NOT EXISTS model_name TEXT');

await query('ALTER TABLE model_configs ALTER COLUMN rpm DROP NOT NULL');
await query('ALTER TABLE model_configs ALTER COLUMN tpm DROP NOT NULL');
await query('ALTER TABLE model_configs ALTER COLUMN rpd DROP NOT NULL');
await query(`
UPDATE model_configs
SET rpm = NULL, tpm = NULL, rpd = NULL, updated_at = now()
WHERE rpm = 1 AND tpm = 1 AND rpd = 1
`);

await query(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Potential database constraint violation in migration

The migration removes the commands that make 'rpm', 'tpm', and 'rpd' columns nullable (DROP NOT NULL), while simultaneously removing those columns from the subsequent INSERT statements. If these columns remain in the schema as NOT NULL and lack default values, the INSERT queries will fail with a constraint violation error. If these fields are deprecated, they should be explicitly dropped from the table using 'ALTER TABLE model_configs DROP COLUMN ...' to ensure schema consistency across environments.

Suggested change
await query(
// If the columns are no longer needed:
await query('ALTER TABLE model_configs DROP COLUMN IF EXISTS rpm');
await query('ALTER TABLE model_configs DROP COLUMN IF EXISTS tpm');
await query('ALTER TABLE model_configs DROP COLUMN IF EXISTS rpd');

`
UPDATE model_configs mc
Expand Down Expand Up @@ -317,14 +308,11 @@ async function ensureModelCatalog() {

await query(
`
INSERT INTO model_configs (model_id, rpm, tpm, rpd, provider, provider_id, model_name, updated_at)
SELECT $1, 10, 131072, 300, 'cloudflare', p.id, $1, now()
INSERT INTO model_configs (model_id, provider, provider_id, model_name, updated_at)
SELECT $1, 'cloudflare', p.id, $1, now()
FROM llm_providers p
WHERE p.name = 'Cloudflare'
ON CONFLICT (model_id) DO UPDATE SET
rpm = EXCLUDED.rpm,
tpm = EXCLUDED.tpm,
rpd = EXCLUDED.rpd,
provider = EXCLUDED.provider,
provider_id = EXCLUDED.provider_id,
model_name = EXCLUDED.model_name,
Expand All @@ -335,14 +323,11 @@ async function ensureModelCatalog() {

await query(
`
INSERT INTO model_configs (model_id, rpm, tpm, rpd, provider, provider_id, model_name, updated_at)
SELECT '@cf/zai-org/glm-4.7-flash', 20, 131072, 600, 'cloudflare', p.id, '@cf/zai-org/glm-4.7-flash', now()
INSERT INTO model_configs (model_id, provider, provider_id, model_name, updated_at)
SELECT '@cf/zai-org/glm-4.7-flash', 'cloudflare', p.id, '@cf/zai-org/glm-4.7-flash', now()
FROM llm_providers p
WHERE p.name = 'Cloudflare'
ON CONFLICT (model_id) DO UPDATE SET
rpm = EXCLUDED.rpm,
tpm = EXCLUDED.tpm,
rpd = EXCLUDED.rpd,
provider = EXCLUDED.provider,
provider_id = EXCLUDED.provider_id,
model_name = EXCLUDED.model_name,
Expand All @@ -352,8 +337,8 @@ async function ensureModelCatalog() {

await query(
`
INSERT INTO model_configs (model_id, rpm, tpm, rpd, provider, provider_id, model_name, updated_at)
SELECT 'gemma-4-31b-it', 15, 1000000, 1500, 'gemini', p.id, 'gemma-4-31b-it', now()
INSERT INTO model_configs (model_id, provider, provider_id, model_name, updated_at)
SELECT 'gemma-4-31b-it', 'gemini', p.id, 'gemma-4-31b-it', now()
FROM llm_providers p
WHERE p.name = 'Google'
ON CONFLICT (model_id) DO UPDATE SET
Expand All @@ -366,8 +351,8 @@ async function ensureModelCatalog() {

await query(
`
INSERT INTO model_configs (model_id, rpm, tpm, rpd, provider, provider_id, model_name, updated_at)
SELECT 'gemma-4-26b-a4b-it', 30, 1000000, 1500, 'gemini', p.id, 'gemma-4-26b-a4b-it', now()
INSERT INTO model_configs (model_id, provider, provider_id, model_name, updated_at)
SELECT 'gemma-4-26b-a4b-it', 'gemini', p.id, 'gemma-4-26b-a4b-it', now()
FROM llm_providers p
WHERE p.name = 'Google'
ON CONFLICT (model_id) DO UPDATE SET
Expand Down
82 changes: 8 additions & 74 deletions src/client/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -903,8 +903,7 @@
gap: 0.625rem !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Excessive use of !important

The CSS relies heavily on !important for almost every property (e.g., lines 906, 917, 924, 994). This creates extreme specificity that makes the styles nearly impossible to override or extend without further !important declarations, leading to a 'specificity war' and making the codebase difficult to maintain.

Suggested change
gap: 0.625rem !important;
Remove !important and instead increase selector specificity or reorganize the CSS cascade to ensure styles are applied correctly.

padding: 0.75rem 0.875rem !important;
border-radius: 0.625rem !important;
border-width: 1px !important;
border-style: solid !important;
border: none !important;
font-family: var(--font-sans) !important;
font-size: 0.8125rem !important;
line-height: 1.45 !important;
Expand All @@ -915,7 +914,6 @@

/* light defaults (overridden per-variant below) */
background: oklch(99.5% 0.004 115) !important;
border-color: oklch(88% 0.008 115) !important;
color: oklch(15% 0.02 115) !important;

/* smooth entrance */
Expand All @@ -924,7 +922,6 @@

.dark .codra-toast {
background: oklch(13% 0.018 115) !important;
border-color: oklch(22% 0.022 115) !important;
color: oklch(94% 0.006 115) !important;
box-shadow:
0 6px 24px oklch(0% 0 0 / 0.5),
Expand Down Expand Up @@ -983,64 +980,9 @@
background: oklch(28% 0.022 115) !important;
}

/* ── SUCCESS ─────────────────────────────────────── */
.codra-toast-success {
background: oklch(98.5% 0.045 115) !important;
border-color: oklch(82% 0.16 115) !important;
color: oklch(28% 0.10 115) !important;
}

.dark .codra-toast-success {
background: oklch(16% 0.08 115) !important;
border-color: oklch(32% 0.14 115) !important;
color: oklch(90% 0.18 115) !important;
}

.codra-toast-success .codra-toast-description {
color: oklch(38% 0.10 115) !important;
opacity: 0.85 !important;
}

.dark .codra-toast-success .codra-toast-description {
color: oklch(72% 0.12 115) !important;
opacity: 0.9 !important;
}

/* ── ERROR ───────────────────────────────────────── */
.codra-toast-error {
background: oklch(98.5% 0.03 25) !important;
border-color: oklch(80% 0.14 25) !important;
color: oklch(32% 0.14 25) !important;
}

.dark .codra-toast-error {
background: oklch(15% 0.07 25) !important;
border-color: oklch(35% 0.14 25) !important;
color: oklch(85% 0.08 25) !important;
}

.codra-toast-error .codra-toast-description {
color: oklch(42% 0.12 25) !important;
opacity: 0.85 !important;
}

.dark .codra-toast-error .codra-toast-description {
color: oklch(68% 0.10 25) !important;
opacity: 0.9 !important;
}

/* ── LOADING ─────────────────────────────────────── */
.codra-toast-loading {
background: oklch(98% 0.004 115) !important;
border-color: oklch(86% 0.010 115) !important;
color: oklch(20% 0.020 115) !important;
}

.dark .codra-toast-loading {
background: oklch(14% 0.020 115) !important;
border-color: oklch(24% 0.025 115) !important;
color: oklch(88% 0.008 115) !important;
}
/* ── SUCCESS / ERROR / LOADING ─────────────────────
Use the default toast text color instead of a
status tint (icon color already conveys status). */

/* spinner inherits accent color */
.codra-toast-loader svg {
Expand All @@ -1049,26 +991,18 @@

/* ── WARNING ─────────────────────────────────────── */
.codra-toast-warning {
background: oklch(98.5% 0.04 65) !important;
border-color: oklch(82% 0.13 65) !important;
color: oklch(35% 0.12 65) !important;
color: oklch(35% 0.12 65) !important;
}

.dark .codra-toast-warning {
background: oklch(16% 0.08 65) !important;
border-color: oklch(35% 0.14 65) !important;
color: oklch(82% 0.14 65) !important;
color: oklch(82% 0.14 65) !important;
}

/* ── INFO ────────────────────────────────────────── */
.codra-toast-info {
background: oklch(98.5% 0.03 250) !important;
border-color: oklch(80% 0.12 250) !important;
color: oklch(30% 0.12 250) !important;
color: oklch(30% 0.12 250) !important;
}

.dark .codra-toast-info {
background: oklch(15% 0.07 250) !important;
border-color: oklch(33% 0.12 250) !important;
color: oklch(80% 0.12 250) !important;
color: oklch(80% 0.12 250) !important;
}
2 changes: 1 addition & 1 deletion src/client/components/features/job-detail/file-finding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function FileFinding({ file }: FileFindingProps) {
</div>
<div className="flex items-center gap-2 shrink-0">
<StatusBadge label={file.fileStatus} />
<StatusBadge label={file.verdict ?? 'comment'} />
{file.fileStatus === 'done' && <StatusBadge label={file.verdict ?? 'comment'} />}
{file.parsedComments.length > 0 && (
<span className="rounded-full bg-primary px-2 py-0.5 text-xs font-bold text-primary-foreground">
{file.parsedComments.length}
Expand Down
Loading
Loading