JavaScript / TypeScript
- ESLint with your shared config
- Prettier for formatting
- typescript-eslint for TypeScript-aware lint rules
tsc --noEmitfor type checking
AI-powered code quality gates are layered verification: an agent that fixes lint and type errors as it writes, a headless review that scans every pull-request diff before a human looks, and continuous monitoring that fails the build when coverage or complexity regresses. Each layer catches the mechanical defects the previous one let through, across Cursor, Claude Code, and Codex.
A null slipped past review, shipped on Friday, and paged you at 2am. The diff looked fine — three reviewers approved it — but nobody noticed the unguarded response.data.user.id on a path that only fires for SSO logins. The Friday before that, a PR introduced a memory leak in the session handler: it passed every test, the reviewer approved it after a glance, and the linter had nothing to say. By Monday the service was eating 4GB of RAM and crashing every six hours.
Human review is the wrong tool for both. Style, type holes, N+1 queries, missing input validation, unhandled rejections, and slow resource leaks are what an agent wired into a quality gate catches every time, before a human ever opens the PR. This article builds that gate across Cursor, Claude Code, and Codex so reviewers spend their attention on architecture and intent instead of playing linter.
.cursor/rules, CLAUDE.md, or AGENTS.md) that every agent checks its own output againsttsc --noEmit on every file the agent edits — with the correct event-keyed schema and stdin file pathYou want defects caught as early and as cheaply as possible, and no single layer is sufficient on its own. That means three, each catching what the one before it let through:
The rest of this article builds each layer. Layers 1 and 2 are where the three tools differ, so they use <Tabs>.
All three tools read a project-level rules file and apply it to everything they generate. The file format and location differ; the content is nearly identical. Keep it in version control so the whole team — and every agent — works from the same standard. Write it as checkable conditions, not aspirations: a line limit and a complexity ceiling can be verified, “write clean code” cannot.
---description: Enterprise Code Quality StandardsalwaysApply: true---## Style- 2-space indentation, max line length 100- Every exported function has a JSDoc block- No `any` without a `// eslint-disable-next-line` and a reason- No magic numbers - use named constants
## Size and complexity- No function longer than 50 lines; no file longer than 300- Cyclomatic complexity under 10 per function
## Architecture- Data access goes through the repository layer, never inline SQL in handlers- Services receive dependencies via constructor injection- All outbound HTTP calls go through the shared `httpClient` wrapper- Error handling follows our Result<T, E> pattern (no bare try/catch)
## Performance- Paginate any endpoint that returns a list- No queries inside loops — batch with `IN (...)` or a join- Memoize pure functions that run on every render
## Security- Parameterized queries only- Validate request bodies with the Zod schema in `schemas/`- Never log tokens, passwords, or full request bodies## Coding Standards
### Style- ESLint config: `.eslintrc.json`; Prettier: `.prettierrc`- TypeScript strict mode; no `any` without an inline justification comment- No `console.log` in committed code — use the `logger` module
### Quality gates- Coverage floor: 80% on changed lines- Cyclomatic complexity limit: 10 (enforced by `eslint-plugin-complexity`)- No function over 50 lines; no file over 300- Every TODO references a ticket: `// TODO(PROJ-1234): ...`
### Before you finish a task- Run `npm run lint && npm run typecheck && npm test`- Add or update tests for new behavior- If a check fails, fix it before moving on — do not report done with a red gate- Update the relevant doc in `docs/` if you changed a public API## Project standards
Codex reads AGENTS.md from the repo root (and merges nested ones insubdirectories). Same rules as the other tools — keep them in sync.
### Style- 2-space indentation, max line length 100, Prettier-formatted- TypeScript strict; no `any` without a justification comment- Use `typescript-eslint` rules, not legacy formatting lint
### Quality gates- 80% coverage on changed lines; complexity limit 10- All new code ships with tests; no lint warnings in new code- Parameterized queries only; validate inputs with Zod
### After every code modification1. Run the file's test suite2. Run the linter on the changed files3. Run type checking4. If any check fails, fix it before proceedingThe most common mistake here is a hook config that silently never runs. Claude Code nests hook arrays under an event name (PostToolUse, PreToolUse) inside the top-level hooks object — a bare top-level hooks array will not load. The matcher is a regex run against tool_name, so it must be cased Write|Edit. And hooks do not receive the edited path in an environment variable; they read JSON on stdin and pull .tool_input.file_path.
Put the logic in a script so the config stays readable:
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "node scripts/quality-check.js" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/format-and-lint.sh" } ] } ] }}#!/usr/bin/env bashset -euo pipefail
# The edited path arrives as JSON on stdin, not as an env var.FILE_PATH=$(jq -r '.tool_input.file_path // empty')[ -z "$FILE_PATH" ] && exit 0
npx prettier --write "$FILE_PATH"npx eslint --fix "$FILE_PATH"
# Type-check only TS files; tsc does the type checking, not a linter.case "$FILE_PATH" in *.ts|*.tsx) npx tsc --noEmit ;;esacPreToolUse is the gate that can refuse a write before it happens — use it for policy checks that should stop an edit (a protected path, a forbidden import). PostToolUse is the fix-up pass. $CLAUDE_PROJECT_DIR is one of the few real hook variables (alongside $CLAUDE_ENV_FILE for SessionStart and $CLAUDE_CODE_REMOTE); wrap it in quotes so paths with spaces survive.
In Cursor, the equivalent is the auto-fix loop: when ESLint errors land in the Problems panel, the agent fixes them and re-runs until clean. Codex applies the same eslint --fix step inside its sandbox when you ask it to “make lint pass” as part of a task.
JavaScript / TypeScript
tsc --noEmit for type checkingPython
Java
Go
The single most useful in-editor habit is killing any the moment the checker points at it — an any is a hole the whole gate reads through. Cursor’s auto-fix loop does this when typed, but the prompt works in all three tools.
A clean result looks like this — the cast becomes a named, checkable contract:
interface UserResponse { id: string; status: 'active' | 'inactive'; metadata: Record<string, unknown>;}
const data = response.data as UserResponse;This is where the gate earns its keep. The setup is genuinely three-tool: each runs a headless agent against the PR diff and posts findings.
Cursor’s built-in PR review is BugBot. Enable it from the dashboard’s GitHub integration, then drop a .cursor/BUGBOT.md at the repo root to steer what it flags (see the review guidelines below). BugBot comments inline on the PR automatically once connected.
Add the GitHub MCP server (remote HTTP — there is no built-in github shorthand, and the transport plus URL are required):
claude mcp add --transport http github https://api.githubcopilot.com/mcp/# Auth via OAuth on first use, or pass a token:# --header "Authorization: Bearer $GITHUB_PAT"For PR automation specifically, install the GitHub App so Claude can be mentioned on PRs:
/install-github-appUse Codex Cloud code review: connect the repo in the Codex Cloud dashboard and enable automatic review on pull requests. Codex reads AGENTS.md for your standards and posts review comments. For ad-hoc local review, run the headless codex exec step shown in the next section.
Both Cursor’s .cursor/BUGBOT.md and a prompt fed to Claude Code or Codex benefit from an explicit checklist. Keep it focused on what humans reliably miss:
# .cursor/BUGBOT.md (or paste into the review prompt)
## Security (block on any of these)- Hardcoded credentials, tokens, or API keys- Unparameterized SQL or string-concatenated queries- Unvalidated request bodies reaching the database- Missing auth check on a protected route- User input rendered without escaping (XSS)
## Correctness- Unhandled promise rejections / missing `await`- Null/undefined dereferences on optional fields- N+1 query patterns (a query inside a `.map`/loop)
## Quality- New code without tests- Functions over 50 lines or complexity over 10- Logging that includes sensitive dataDrop this into any repo. It runs on pull requests and posts the agent’s findings. Note actions/checkout@v6 — @v3 is deprecated and forces JavaScript actions onto an unsupported Node runtime.
BugBot runs as a hosted GitHub integration, so there is no CI YAML to maintain — it reviews PRs automatically once enabled. Use the Claude Code or Codex tab if you want the review step to live in your own workflow file instead.
name: AI Code Reviewon: [pull_request]
jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: AI review env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | git diff origin/${{ github.base_ref }}...HEAD > diff.patch claude -p "Review the diff in diff.patch against .cursor/BUGBOT.md. \ Report only real defects as 'file:line — issue — fix', \ grouped by Security / Correctness / Quality. \ If nothing is wrong, say 'No blocking issues.'" \ --output-format json > review.jsonname: AI Code Reviewon: [pull_request]
jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: AI review env: CODEX_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | git diff origin/${{ github.base_ref }}...HEAD > diff.patch codex exec --sandbox read-only -c approval_policy=never \ "Review diff.patch against AGENTS.md. Report only real \ defects as 'file:line — issue — fix', grouped by \ Security / Correctness / Quality."This non-interactive review cannot surface a new approval prompt. Use approval_policy=never only with a trusted, least-privilege CI identity; read-only remains the enforced sandbox boundary and any action requiring more access fails.
For everyday pull requests, keep the review narrow and stack-aware. The value is a short list of real defects, not a thorough essay.
A change to a payment path or a shared abstraction deserves more than the routine pass. This prompt adds the dimensions the routine one deliberately skips — architecture, maintainability, testability — and makes the agent classify each finding so you can triage it.
When one pass produces a shallow, blended answer — a common failure on a large or unfamiliar diff — run the dimensions as separate conversations instead. Each lens gets the model’s full attention, and you can throw away the ones that come back empty.
Run each lens as a separate Agent conversation for depth:
Lens 1 - Correctness: Review /src/services/payment.ts changes.Assume every input is adversarial. Find every way this code couldproduce incorrect results, crash, or behave unexpectedly.Lens 2 - Performance: Same file. Assume 10,000 requests per second.Find bottlenecks, memory leaks, and unnecessary allocations.Lens 3 - Security: Same file. You are a penetration tester.Find every way to exploit this code.Use sub-agents to run multiple review lenses in parallel:
claude "Review the changes in the current git diff through five lenses.For each lens, provide separate findings:
1. CORRECTNESS: Logic errors, edge cases, race conditions2. PERFORMANCE: N+1 queries, memory leaks, unnecessary computation3. SECURITY: Injection, auth bypass, data exposure4. MAINTAINABILITY: Complexity, naming, documentation gaps5. TESTABILITY: Missing tests, untestable patterns, flaky test risks
Rank all findings by severity and present the top 10 across all lenses."Perform a five-lens code review on the changes in this PR:1. Correctness: Will this produce wrong results for any valid input?2. Performance: Will this degrade under production load?3. Security: Can this be exploited by a malicious user?4. Maintainability: Will the next developer understand and modify this safely?5. Testability: Are there edge cases that the tests do not cover?
Post findings as inline PR comments at the relevant lines.Real monitoring means real tooling, not a model recalling numbers. SonarQube (or SonarCloud) is the standard: it computes coverage, cyclomatic complexity, and duplication on every build and tracks the trend. Wire it into the same workflow:
# add to .github/workflows/ai-review.yml - name: SonarQube scan uses: SonarSource/sonarqube-scan-action@v6 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: args: > -Dsonar.qualitygate.wait=trueThe qualitygate.wait=true flag blocks the PR if the project’s Sonar quality gate fails (for example, coverage on new code below 80% or a new blocker-severity issue). That is your enforcement point — concrete, measured, and not something an agent can talk its way around.
For the “what’s the AI’s read on this?” angle, feed Sonar’s findings to the agent rather than asking it to invent metrics:
A monthly health pass covers what a per-PR gate structurally cannot see: drift. Run it in a session where the agent can actually measure — a coverage report on disk, git log, a dead-code scan, a duplication scan — and say so in the prompt. Asked cold, a model will happily produce a plausible dashboard of numbers it never computed, which is worse than no dashboard.
Coverage percentage does not measure quality; it measures which lines ran. These are the numbers that move when the gate is actually working:
| Metric | What It Reveals | Target |
|---|---|---|
| Mutation score | Tests actually catch bugs, not just execute code | > 75% |
| Mean time to detect | How quickly bugs are found after introduction | < 1 sprint |
| Escaped defect rate | Bugs that reach production | < 2% of changes |
| Review turnaround | How long PRs wait for review | < 4 hours |
| Rework rate | PRs that need > 2 review rounds | < 15% |
| Build reliability | CI pipeline pass rate | > 95% |
Mutation score is the one worth standing up first, because it is the only metric on the list that cannot be gamed by writing more tests that assert nothing.
A common production regression is a query or endpoint that works fine in review and falls over under load. Bake load testing into the gate with k6 — the thresholds are real and make the test pass or fail on its own.
The generated test encodes the thresholds as gate conditions, so a regression turns the CI step red:
import http from 'k6/http';import { check } from 'k6';
export const options = { stages: [ { duration: '2m', target: 200 }, { duration: '5m', target: 200 }, { duration: '2m', target: 0 }, ], thresholds: { http_req_duration: ['p(95)<500'], http_req_failed: ['rate<0.01'], },};
export default function () { const res = http.post( `${__ENV.BASE_URL}/api/checkout`, JSON.stringify({ cartId: 'c_1', paymentMethodId: 'pm_1', idempotencyKey: `${__VU}-${__ITER}` }), { headers: { 'Content-Type': 'application/json' }, tags: { name: 'checkout' } }, ); check(res, { 'status 200': (r) => r.status === 200 });}A gate that only one repo runs is a personal habit. Distributing it means the standards themselves become a versioned artifact:
Create a shared config package
One package — in your monorepo or published to npm — holding the ESLint, TypeScript, and Prettier configs plus the AI rules files.
Distribute through package management
Each project extends the shared config. Local overrides must be documented and approved, not quietly added.
Enforce in CI
The pipeline checks that shared configs have not been overridden without approval.
Point the AI rules at the shared standard
.cursor/rules, CLAUDE.md, and AGENTS.md reference the shared standards document rather than each drifting its own copy.
Review health monthly
Run the codebase health pass on a schedule and compare teams against the shared benchmarks, so a regression is a trend line rather than an incident.
Once the prompts above prove useful, save them as reusable slash commands. A file at .claude/commands/security-audit.md becomes the /security-audit command inside an interactive Claude Code session (subdirectories add namespacing — .claude/commands/review/pr.md is /review:pr). Invoke it in the REPL:
> /security-auditwith the command file holding your OWASP-focused prompt. Cursor exposes the same idea through saved prompts; Codex through AGENTS.md workflows and custom prompts.