diff --git a/.env.example b/.env.example index 30dc12c..95a7506 100644 --- a/.env.example +++ b/.env.example @@ -3,8 +3,6 @@ ANTHROPIC_API_KEY=your_anthropic_api_key_here # GitHub GITHUB_TOKEN=your_github_personal_access_token_here TARGET_GITHUB_USERNAME=your_github_username_here -# PR review: absolute path to a local clone of the repo whose PRs you review -REPO_PATH=/absolute/path/to/local/clone/of/the/repo # Your personal oh-my-workers DB DATABASE_URL=postgresql://user:password@localhost:5432/work_coordinator # Company DB (update these when you change jobs) diff --git a/package.json b/package.json index 37f9d9a..0a42408 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "news": "pnpm run dev --job=news", "jobs": "pnpm run dev --list-jobs", "seed-mock": "node --loader ts-node/esm src/scripts/seed-mock-users.ts", - "review:dev": "node --loader ts-node/esm src/scripts/run-pr-review.ts", "format": "prettier --write \"src/**/*.ts\"", "format:check": "prettier --check \"src/**/*.ts\"" }, diff --git a/packages/pr-review/.env.example b/packages/pr-review/.env.example new file mode 100644 index 0000000..ef23fc1 --- /dev/null +++ b/packages/pr-review/.env.example @@ -0,0 +1,9 @@ +# Anthropic API key (the review model runs on Claude) +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# GitHub token — fine-grained, read-only: Contents + Pull requests on the repo you review +GITHUB_TOKEN=your_github_personal_access_token_here + +# Absolute path to a local clone of the repo whose PRs you review. +# Check out the PR's branch there so the surrounding-code context matches the PR. +REPO_PATH=/absolute/path/to/local/clone/of/the/repo diff --git a/packages/pr-review/README.md b/packages/pr-review/README.md new file mode 100644 index 0000000..ed78dd8 --- /dev/null +++ b/packages/pr-review/README.md @@ -0,0 +1,167 @@ +# @oh-my-worker/pr-review + +AI code-review CLI. Point it at a GitHub pull request and it reviews the changes for **bugs, security, and performance** issues — reading the surrounding code from a local clone so its judgement is grounded in real context, not just the diff. + +```bash +omw-review https://github.com/acme/widgets/pull/42 +``` + +--- + +## What you need before you start + +1. **Node.js 18+** — check with `node --version`. +2. **ripgrep** (`rg`) on your PATH — the tool uses it to search code. macOS: `brew install ripgrep`. Check with `rg --version`. +3. **An Anthropic API key** — the review runs on Claude. Get one at . +4. **A GitHub token** (fine-grained, read-only) — so the tool can fetch the PR. See [Get a GitHub token](#get-a-github-token) below. +5. **A local clone** of the repo whose PRs you review — the tool reads files from it for context. + +--- + +## Install + +```bash +npm install -g @oh-my-worker/pr-review +``` + +This gives you the `omw-review` command everywhere on your machine. + +--- + +## Configure (the important part) + +The tool needs **three** settings. Because you installed it **globally** (you run `omw-review` from anywhere), the cleanest place for these is your **shell environment** — not a `.env` file. (A `.env` file is only read from the folder you happen to run the command in, which is fragile for a global tool.) + +| Variable | What it is | Where to put it | +| --- | --- | --- | +| `ANTHROPIC_API_KEY` | Your Anthropic key (rarely changes) | Shell profile (`~/.zshrc`) | +| `GITHUB_TOKEN` | Read-only GitHub token (rarely changes) | Shell profile (`~/.zshrc`) | +| `REPO_PATH` | Absolute path to the local clone you're reviewing (changes per repo) | Per command, at run time | + +### Step 1 — put the two secrets in your shell profile + +Open `~/.zshrc` (the default on macOS; use `~/.bashrc` on bash) and add: + +```bash +export ANTHROPIC_API_KEY="sk-ant-..." +export GITHUB_TOKEN="github_pat_..." +``` + +Then reload it (or just open a new terminal): + +```bash +source ~/.zshrc +``` + +Now those two are set in every terminal session — you only do this once. + +### Step 2 — give `REPO_PATH` when you run a review + +`REPO_PATH` points at the local clone of the repo the PR belongs to. The easiest pattern is to `cd` into that clone and use the current directory: + +```bash +cd ~/work/the-company-repo # the clone +git fetch origin # make sure you have the PR's branch +git checkout the-pr-branch # so the context files match the PR +REPO_PATH="$(pwd)" omw-review https://github.com/acme/widgets/pull/42 +``` + +> If you **only ever review one repo**, you can instead add `export REPO_PATH="/abs/path/to/that/repo"` to `~/.zshrc` and skip typing it each time. + +--- + +## Get a GitHub token + +Use a **fine-grained, read-only** token — minimal access, so it's safe even if it leaks. + +1. GitHub → **Settings → Developer settings → Fine-grained tokens → Generate new token**. +2. **Repository access** → *Only select repositories* → pick the repo you review. +3. **Permissions** (read-only): + - Contents → **Read** + - Pull requests → **Read** +4. Generate, copy the token, and paste it into `~/.zshrc` as `GITHUB_TOKEN` (Step 1 above). + +--- + +## Usage + +```bash +REPO_PATH="$(pwd)" omw-review +``` + +The tool will: +1. Fetch the PR's changed files and diffs from GitHub. +2. Read related files from `REPO_PATH` (callers, definitions, types) to understand the change in context. +3. Print a ranked list of findings — each with **severity · category · `file:line` · explanation · suggested fix**. + +It reviews for **bugs, security, and performance** only — no style nits. + +--- + +## Troubleshooting + +| Message | What it means / fix | +| --- | --- | +| `Missing required environment variable(s): ...` | One of the three isn't set. Re-check Step 1, and that you passed `REPO_PATH`. Run `echo $ANTHROPIC_API_KEY` to confirm it's exported. | +| `Invalid GitHub PR URL: ...` | The URL isn't a PR link. It must look like `https://github.com///pull/`. | +| `... 404` while fetching the PR | Your `GITHUB_TOKEN` can't see that repo. Make sure the token's *repository access* includes it, with Contents + Pull requests read. | +| `Refusing to read "..." — it resolves outside REPO_PATH` | A safety guard — the tool only reads files inside `REPO_PATH`. Usually harmless. | +| `rg: command not found` / search failures | Install ripgrep (`brew install ripgrep`). | + +--- + +## Programmatic use + +You can also call it from your own code instead of the CLI: + +```ts +import { reviewPullRequest, formatReview } from '@oh-my-worker/pr-review' + +const result = await reviewPullRequest('https://github.com/acme/widgets/pull/42') +console.log(formatReview(result)) +``` + +The same environment variables apply. + +--- + +## Releasing a new version (for maintainers) + +> This section is for whoever **publishes** the package — if you're just using the tool, you can ignore it. + +Versions follow [semver](https://semver.org): `MAJOR.MINOR.PATCH` — **patch** = bugfix, **minor** = new backward-compatible feature, **major** = breaking change. + +**First time only** — log in to npm and make sure you own the `@oh-my-worker` scope (create a free npm org named `oh-my-worker`, or rename the package to your own `@username` scope): + +```bash +npm whoami # logged in? if not, run: npm login +``` + +**Each release — four commands:** + +```bash +cd packages/pr-review +npm version patch # 1. bumps package.json (e.g. 0.1.0 → 0.1.1) AND creates a git commit + tag "v0.1.1" +git push --follow-tags # 2. pushes the commit and the new tag to GitHub +npm publish # 3. builds (via prepublishOnly) and publishes to npm +gh release create v0.1.1 --title "v0.1.1" --notes "What changed in this release" # 4. GitHub Release +``` + +- **Step 1 is the key one:** `npm version` keeps the npm version and the git tag identical (`v0.1.1`). That tag is what a GitHub Release attaches to — it's the "Latest" entry on the repo's **Releases** page. +- Use `npm version minor` or `npm version major` instead of `patch` depending on the change. +- **Step 4** can also be done in the GitHub UI: **repo → Releases → Draft a new release → pick the tag → write notes**. The number of releases shown there is just your number of version tags over time. + +**Monorepo note:** since `pr-review` is the only publishable package here, plain `vX.Y.Z` tags are fine. If you add a second publishable package later, switch to prefixed tags like `pr-review-v0.1.1` to avoid tag collisions. + +**Automating it (optional):** add a `.github/workflows/release.yml` that triggers on tag push (`on: push: tags: ['v*']`), runs the build, then `npm publish` (using an `NPM_TOKEN` repo secret) and `gh release create`. Then every release is just `npm version patch && git push --follow-tags` — CI does the publish and the GitHub Release for you. + +--- + +## Notes + +- The review is an **assistant, not an oracle** — treat findings as leads to verify, not gospel. It can miss issues and occasionally flag non-issues. +- Cost and time scale with PR size (each review uses Anthropic API tokens). + +## License + +ISC diff --git a/packages/pr-review/package.json b/packages/pr-review/package.json new file mode 100644 index 0000000..5391896 --- /dev/null +++ b/packages/pr-review/package.json @@ -0,0 +1,53 @@ +{ + "name": "@oh-my-worker/pr-review", + "version": "0.1.0", + "description": "AI code-review CLI: reviews a GitHub pull request for bugs, security and performance, using a local clone for surrounding-code context.", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "omw-review": "dist/cli.js" + }, + "files": [ + "dist", + "README.md", + ".env.example" + ], + "engines": { + "node": ">=18" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "node --loader ts-node/esm src/cli.ts", + "prepublishOnly": "pnpm run build" + }, + "keywords": [ + "code-review", + "pull-request", + "github", + "ai", + "llm", + "claude", + "langchain", + "cli", + "typescript" + ], + "author": "damengrandom", + "license": "ISC", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@langchain/anthropic": "^1.3.25", + "@langchain/core": "^1.1.36", + "@octokit/rest": "^22.0.1", + "dotenv": "^17.3.1", + "langchain": "^1.2.37", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "ts-node": "^10.9.2", + "typescript": "^6.0.2" + } +} diff --git a/packages/pr-review/src/cli.ts b/packages/pr-review/src/cli.ts new file mode 100644 index 0000000..d4f3700 --- /dev/null +++ b/packages/pr-review/src/cli.ts @@ -0,0 +1,43 @@ +#!/usr/bin/env node +import 'dotenv/config' +import { reviewPullRequest } from './pr-review.agent.js' +import { formatReview } from './review-formatter.js' + +// Deterministic guardrail #1: fail fast (with a clear message) if the environment is not +// configured, instead of crashing deep inside a tool call later. This is plain code, not an agent. +function assertEnv(): void { + const required = ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN', 'REPO_PATH'] + const missing = required.filter((name) => !process.env[name]) + + if (missing.length > 0) { + console.error(`❌ Missing required environment variable(s): ${missing.join(', ')}`) + console.error(' Set them in your environment or a .env file (see .env.example).') + process.exit(1) + } +} + +async function main(): Promise { + // process.argv = ['node', '/path/to/cli.js', '', ...]. The first real argument is index 2. + const prUrl = process.argv[2] + + // Deterministic guardrail #2: no URL provided → print usage and exit. + if (!prUrl) { + console.error('Usage: omw-review ') + console.error('Example: omw-review https://github.com/acme/widgets/pull/42') + process.exit(1) + } + + assertEnv() + + // reviewPullRequest parses/validates the URL (throws on a bad one), runs the agent, and + // returns the structured findings. The formatter turns them into the coloured terminal report. + const result = await reviewPullRequest(prUrl) + + console.log('\n' + formatReview(result) + '\n') +} + +main().catch((err) => { + console.error('❌ PR review failed:', err instanceof Error ? err.message : err) + + process.exit(1) +}) diff --git a/packages/pr-review/src/constants.ts b/packages/pr-review/src/constants.ts new file mode 100644 index 0000000..29933ba --- /dev/null +++ b/packages/pr-review/src/constants.ts @@ -0,0 +1,5 @@ +// Model used for the review reasoning. Sonnet balances code-reasoning quality and cost; +export const REVIEW_LLM = 'claude-haiku-4-5' + +// Caps how many times the agent may read/search context before it must produce a verdict. +export const MAX_REVIEW_TOOL_CALLS = 30 diff --git a/packages/pr-review/src/index.ts b/packages/pr-review/src/index.ts new file mode 100644 index 0000000..373aa16 --- /dev/null +++ b/packages/pr-review/src/index.ts @@ -0,0 +1,6 @@ +// Public library API — lets other code `import { reviewPullRequest } from '@oh-my-worker/pr-review'`. +// The CLI (src/cli.ts) is a separate entry exposed via the package `bin`. +export { reviewPullRequest, prReviewAgent } from './pr-review.agent.js' +export { formatReview } from './review-formatter.js' +export { parsePrUrl } from './pr-url.js' +export { ReviewResultSchema, ReviewFindingSchema, type ReviewResult, type ReviewFinding } from './schemas.js' diff --git a/src/agent/pr-review.agent.ts b/packages/pr-review/src/pr-review.agent.ts similarity index 75% rename from src/agent/pr-review.agent.ts rename to packages/pr-review/src/pr-review.agent.ts index 0132e69..c372d35 100644 --- a/src/agent/pr-review.agent.ts +++ b/packages/pr-review/src/pr-review.agent.ts @@ -1,17 +1,16 @@ import { createAgent, toolCallLimitMiddleware } from 'langchain' import { ChatAnthropic } from '@langchain/anthropic' import { AIMessage } from '@langchain/core/messages' -import { getPrDiffTool, readFileTool, searchCodeTool } from '../tools/pr-review.tool.js' +import { getPrDiffTool, readFileTool, searchCodeTool } from './pr-review.tool.js' import { PR_REVIEW_PROMPT } from './prompt.js' -// import { REVIEW_LLM, MAX_REVIEW_TOOL_CALLS } from '../constants/index.js' -import { DEFAULT_LLM, MAX_REVIEW_TOOL_CALLS } from '../constants/index.js' -import { ReviewResultSchema, type ReviewResult } from '../schemas/index.js' -import { parsePrUrl } from '../utils/pr-url.js' +import { REVIEW_LLM, MAX_REVIEW_TOOL_CALLS } from './constants.js' +import { ReviewResultSchema, type ReviewResult } from './schemas.js' +import { parsePrUrl } from './pr-url.js' -const llm = new ChatAnthropic({ model: DEFAULT_LLM, temperature: 0 }) +const llm = new ChatAnthropic({ model: REVIEW_LLM, temperature: 0 }) -// Unlike the single-call fetch agents, the reviewer loops: read the diff, pull in -// context with read_file/search_code, then judge. The tool-call limit caps that loop. +// The reviewer loops: read the diff, pull in context with read_file/search_code, then judge. +// The tool-call limit caps that loop. // // NOTE: we deliberately do NOT use createAgent's `responseFormat` here. In this langchain // build, combining structured-output machinery with an afterModel middleware produces @@ -27,7 +26,7 @@ export const prReviewAgent = createAgent({ // A separate, single-shot model call that turns the agent's free-text review into the // typed ReviewResult. Runs outside the agent graph, so it avoids the jumpTo conflict. -const structuredLlm = new ChatAnthropic({ model: DEFAULT_LLM, temperature: 0 }).withStructuredOutput(ReviewResultSchema) +const structuredLlm = new ChatAnthropic({ model: REVIEW_LLM, temperature: 0 }).withStructuredOutput(ReviewResultSchema) // Extract the text of the agent's final assistant message (content may be a string or // an array of content blocks). @@ -43,7 +42,7 @@ function finalMessageText(messages: Array<{ content: unknown }>): string { return '' } -// Orchestrator shared by the Task 1 dev script and the future Task 2 CLI. +// Orchestrator: parse the URL, run the review agent, then structure its findings. export async function reviewPullRequest(prUrl: string): Promise { const { owner, repo, number } = parsePrUrl(prUrl) diff --git a/src/tools/pr-review.tool.ts b/packages/pr-review/src/pr-review.tool.ts similarity index 95% rename from src/tools/pr-review.tool.ts rename to packages/pr-review/src/pr-review.tool.ts index ac8ea91..55ec827 100644 --- a/src/tools/pr-review.tool.ts +++ b/packages/pr-review/src/pr-review.tool.ts @@ -26,13 +26,10 @@ function resolveInsideRepo(relativePath: string): string { const root = realpathSync(repoRoot()) const resolved = path.resolve(root, relativePath) - // Follow symlinks for paths that exist. If the path doesn't exist there is nothing to read, - // so the lexical check on `resolved` is sufficient (path.resolve already normalises any '..'). let real = resolved try { real = realpathSync(resolved) } catch { - // non-existent path — fall through to the lexical check below throw new Error(`Refusing to read "${relativePath}" — it does not exist.`) } diff --git a/src/utils/pr-url.ts b/packages/pr-review/src/pr-url.ts similarity index 83% rename from src/utils/pr-url.ts rename to packages/pr-review/src/pr-url.ts index a54b5f1..12d6415 100644 --- a/src/utils/pr-url.ts +++ b/packages/pr-review/src/pr-url.ts @@ -1,5 +1,4 @@ // Parses a GitHub pull request URL into its owner / repo / number parts. -// Basic validation only — full guardrails (size limits, richer input handling) are deferred to a later task. export type ParsedPr = { owner: string diff --git a/packages/pr-review/src/prompt.ts b/packages/pr-review/src/prompt.ts new file mode 100644 index 0000000..cce20c1 --- /dev/null +++ b/packages/pr-review/src/prompt.ts @@ -0,0 +1,25 @@ +export const PR_REVIEW_PROMPT = `You are a senior code reviewer with deep expertise in TypeScript, JavaScript, Node.js, Vue.js and NestJS. You review a single pull request and report only genuine BUGS, SECURITY issues, and PERFORMANCE problems. Do NOT comment on style, formatting, or naming. + +You have three tools: +- get_pr_diff — fetch the PR's changed files and diffs. Call this FIRST. +- read_file — read any file from the local checkout (callers, definitions, types, config). +- search_code — find where a symbol is defined or used across the codebase. + +How to review — investigate before judging: +1. Call get_pr_diff to see exactly what changed. +2. For each meaningful change, DO NOT judge from the diff alone. Pull in context first: + - read_file the file being changed (the diff only shows a slice) and the files/functions/types it touches. + - search_code for callers and definitions to understand how the changed code is actually used. +3. Only after you understand the surrounding code, decide whether something is a real issue. Examples worth reporting: + - Bugs: null/undefined dereferences, wrong/edge-case logic, broken contracts with callers, unhandled promise rejections, incorrect async/await, off-by-one, type mismatches that survive at runtime. + - Security: injection (SQL/command/XSS), missing authz/authn, secret/PII leakage, unsafe deserialization, SSRF, path traversal. + - Performance: N+1 queries, unbounded loops/allocations, blocking the event loop, missing pagination, redundant network/db calls. + +Rules: +- Report a finding ONLY if you are confident it is a real problem after inspecting the surrounding code. When in doubt, leave it out — false positives waste the reviewer's time. +- Every finding must cite the specific file and line in the changed code. +- Explain WHY it is a problem, referencing the context you read (e.g. "parseUser() returns null when the row is missing — see src/db/user.ts:42"). +- Give a concrete suggested fix. +- If you find no genuine issues, say so clearly. + +When done investigating, write your final answer as plain text: a one-paragraph overall summary, then a numbered list of findings. For EACH finding include severity (critical/high/medium/low), category (bug/security/performance), the file path and line number, a short title, an explanation of why it is a real problem, and a concrete suggested fix.` diff --git a/src/utils/review-formatter.ts b/packages/pr-review/src/review-formatter.ts similarity index 95% rename from src/utils/review-formatter.ts rename to packages/pr-review/src/review-formatter.ts index 05e6651..d7dad6f 100644 --- a/src/utils/review-formatter.ts +++ b/packages/pr-review/src/review-formatter.ts @@ -1,4 +1,4 @@ -import type { ReviewResult, ReviewFinding } from '../schemas/index.js' +import type { ReviewResult, ReviewFinding } from './schemas.js' // Minimal ANSI colors — no extra dependency. const COLORS = { diff --git a/packages/pr-review/src/schemas.ts b/packages/pr-review/src/schemas.ts new file mode 100644 index 0000000..a834d76 --- /dev/null +++ b/packages/pr-review/src/schemas.ts @@ -0,0 +1,20 @@ +import { z } from 'zod' + +// A single review finding: a real bug, security, or performance issue with a precise location. +export const ReviewFindingSchema = z.object({ + severity: z.enum(['critical', 'high', 'medium', 'low']), + category: z.enum(['bug', 'security', 'performance']), + file: z.string().describe('Repo-relative path of the file the issue is in'), + line: z.number().describe('Line number in the changed file where the issue occurs'), + title: z.string().describe('Short one-line summary of the issue'), + explanation: z.string().describe('Why this is a real issue, referencing the surrounding code that was inspected'), + suggestion: z.string().describe('Concrete fix or change to resolve the issue'), +}) + +export const ReviewResultSchema = z.object({ + summary: z.string().describe('One-paragraph overall assessment of the PR'), + findings: z.array(ReviewFindingSchema), +}) + +export type ReviewFinding = z.infer +export type ReviewResult = z.infer diff --git a/packages/pr-review/tsconfig.json b/packages/pr-review/tsconfig.json new file mode 100644 index 0000000..4084aca --- /dev/null +++ b/packages/pr-review/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "declaration": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3d57d8..4d62d5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,37 @@ importers: specifier: ^6.0.2 version: 6.0.2 + packages/pr-review: + dependencies: + '@langchain/anthropic': + specifier: ^1.3.25 + version: 1.3.25(@langchain/core@1.1.36) + '@langchain/core': + specifier: ^1.1.36 + version: 1.1.36 + '@octokit/rest': + specifier: ^22.0.1 + version: 22.0.1 + dotenv: + specifier: ^17.3.1 + version: 17.3.1 + langchain: + specifier: ^1.2.37 + version: 1.2.37(@langchain/core@1.1.36) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@types/node': + specifier: ^25.5.0 + version: 25.5.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@25.5.0)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + packages: '@anthropic-ai/sdk@0.74.0': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..18ec407 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/src/agent/prompt.ts b/src/agent/prompt.ts index 97ab276..f36d7db 100644 --- a/src/agent/prompt.ts +++ b/src/agent/prompt.ts @@ -54,29 +54,3 @@ When curating: Call curate_trending_repos with the result immediately.` export const TRENDING_TELEGRAM_PROMPT = `You are a Telegram delivery agent. Your only job is to call send_trending_telegram with the repos provided. Send it immediately and return the result.` - -export const PR_REVIEW_PROMPT = `You are a senior code reviewer with deep expertise in TypeScript, JavaScript, Node.js, Vue.js and NestJS. You review a single pull request and report only genuine BUGS, SECURITY issues, and PERFORMANCE problems. Do NOT comment on style, formatting, or naming. - -You have three tools: -- get_pr_diff — fetch the PR's changed files and diffs. Call this FIRST. -- read_file — read any file from the local checkout (callers, definitions, types, config). -- search_code — find where a symbol is defined or used across the codebase. - -How to review — investigate before judging: -1. Call get_pr_diff to see exactly what changed. -2. For each meaningful change, DO NOT judge from the diff alone. Pull in context first: - - read_file the file being changed (the diff only shows a slice) and the files/functions/types it touches. - - search_code for callers and definitions to understand how the changed code is actually used. -3. Only after you understand the surrounding code, decide whether something is a real issue. Examples worth reporting: - - Bugs: null/undefined dereferences, wrong/edge-case logic, broken contracts with callers, unhandled promise rejections, incorrect async/await, off-by-one, type mismatches that survive at runtime. - - Security: injection (SQL/command/XSS), missing authz/authn, secret/PII leakage, unsafe deserialization, SSRF, path traversal. - - Performance: N+1 queries, unbounded loops/allocations, blocking the event loop, missing pagination, redundant network/db calls. - -Rules: -- Report a finding ONLY if you are confident it is a real problem after inspecting the surrounding code. When in doubt, leave it out — false positives waste the reviewer's time. -- Every finding must cite the specific file and line in the changed code. -- Explain WHY it is a problem, referencing the context you read (e.g. "parseUser() returns null when the row is missing — see src/db/user.ts:42"). -- Give a concrete suggested fix. -- If you find no genuine issues, say so clearly. - -When done investigating, write your final answer as plain text: a one-paragraph overall summary, then a numbered list of findings. For EACH finding include severity (critical/high/medium/low), category (bug/security/performance), the file path and line number, a short title, an explanation of why it is a real problem, and a concrete suggested fix.` diff --git a/src/assets/images/code-review-demo.png b/src/assets/images/code-review-demo.png new file mode 100644 index 0000000..0d0d01c Binary files /dev/null and b/src/assets/images/code-review-demo.png differ diff --git a/src/constants/index.ts b/src/constants/index.ts index c27632f..f1076f4 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,6 +1,4 @@ export const DEFAULT_LLM = 'claude-haiku-4-5' -export const REVIEW_LLM = 'claude-sonnet-4-6' // stronger model for code review reasoning (bugs/security/performance) -export const MAX_REVIEW_TOOL_CALLS = 30 // cap how many times the review agent can read/search context before deciding export const COMPANY_CLEANUP_TABLE = 'mockTestUsers' export const COMPANY_CLEANUP_THRESHOLD_DAYS = '30' export const DEFAULT_CRONJOB_TIME = '0 17 * * *' diff --git a/src/schemas/index.ts b/src/schemas/index.ts index f11c307..9a5122f 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -78,22 +78,6 @@ export const TrendingRepoLogSchema = TrendingRepoSchema.extend({ updated_at: z.string(), }) -// --- PR code review (bugs + security + performance) --- -export const ReviewFindingSchema = z.object({ - severity: z.enum(['critical', 'high', 'medium', 'low']), - category: z.enum(['bug', 'security', 'performance']), - file: z.string().describe('Repo-relative path of the file the issue is in'), - line: z.number().describe('Line number in the changed file where the issue occurs'), - title: z.string().describe('Short one-line summary of the issue'), - explanation: z.string().describe('Why this is a real issue, referencing the surrounding code that was inspected'), - suggestion: z.string().describe('Concrete fix or change to resolve the issue'), -}) - -export const ReviewResultSchema = z.object({ - summary: z.string().describe('One-paragraph overall assessment of the PR'), - findings: z.array(ReviewFindingSchema), -}) - // TypeScript types inferred from schemas export type GitHubDigest = z.infer export type GithubKpiInput = z.infer @@ -103,5 +87,3 @@ export type DiaryEntry = z.infer export type CleanupResult = z.infer export type TrendingRepo = z.infer export type TrendingRepoLog = z.infer -export type ReviewFinding = z.infer -export type ReviewResult = z.infer diff --git a/src/scripts/run-pr-review.ts b/src/scripts/run-pr-review.ts deleted file mode 100644 index f1abf49..0000000 --- a/src/scripts/run-pr-review.ts +++ /dev/null @@ -1,17 +0,0 @@ -import 'dotenv/config' -import { reviewPullRequest } from '../agent/pr-review.agent.js' -import { formatReview } from '../utils/review-formatter.js' - -// Task 1: hardcode the PR URL here. CLI arg parsing comes in Task 2. -// Use a real PR you can access; make sure REPO_PATH points at a local clone with the PR's branch checked out. -const PR_URL = 'https://github.com/DamengRandom/oh-my-workers/pull/3/changes' - -async function main(): Promise { - const result = await reviewPullRequest(PR_URL) - console.log('\n' + formatReview(result) + '\n') -} - -main().catch((err) => { - console.error('Fatal error during PR review:', err instanceof Error ? err.message : err) - process.exit(1) -})