As an indie developer, getting a second pair of eyes on your code is a rare luxury. I maintain my own apps and several blogs single-handedly, so for years the person approving my pull requests has been, well, me. More often than not I discovered the things I had missed only after something broke in production.
That is exactly why a tool that can mechanically stand in for a second reviewer has earned a place in my workflow. Antigravity Editor's AI Code Review fills that role: it combines static analysis with machine learning to flag bugs, security vulnerabilities, and performance problems the moment you commit.
In this article I walk through what I learned by actually using it day to day — which mode to start from, how to live with false positives, and how to fold it into a pull-request workflow.
Core Capabilities of AI Code Review
What Problems Does It Solve?
Antigravity's AI code review covers seven distinct analysis dimensions:
- Security Analysis — SQL injection, cross-site scripting (XSS), authentication bypasses, cryptographic weaknesses
- Performance Optimization — Inefficient algorithms, memory leaks, N+1 query patterns, suboptimal caching
- Code Quality — Naming conventions, cyclomatic complexity, dead code, code duplication
- Bug Detection — Logic errors, type safety violations, exception handling gaps, off-by-one errors
- Architecture Review — Design pattern violations, poor separation of concerns, circular dependencies
- Test Coverage — Untested code paths, missing edge case tests, insufficient assertions
- Maintainability — Missing documentation, overly complex branches, magic numbers, unclear variable names
AI vs Manual Code Review
| Aspect | AI Review | Manual Review |
|---|---|---|
| Execution Time | Seconds to minutes | Hours to days |
| Consistency | Identical standards applied | Variable by reviewer |
| Security Detection Rate | 85% accuracy | 60% accuracy |
| Scalability | Unlimited | Team-dependent |
| Context Awareness | Full project understanding | File-local perspective |
| 24/7 Availability | Yes | No |
| Speed of Feedback | Immediate | Delayed |
Getting Started with Code Review
Quick Start: Review Your First File
Open any file in Antigravity Editor and click the "Code Review" button in the sidebar. Within seconds, you'll receive a comprehensive analysis.
// src/auth/authenticate.ts
export async function authenticateUser(email: string, password: string) {
const user = await db.query(
'SELECT * FROM users WHERE email = ?',
[email]
);
if (user && user.password === md5(password)) {
const token = jwt.sign({ id: user.id }, 'secret-key');
return { success: true, token };
}
return { success: false };
}AI Code Review Output:
🔴 [CRITICAL] Weak Password Hashing
Line 8: MD5 is cryptographically broken and unsuitable for password storage.
Recommendation: Use bcrypt, scrypt, or argon2 with salt.
🔴 [CRITICAL] Hardcoded Secret
Line 10: JWT secret is hardcoded in source. Exposes secret in version control.
Fix: Move to environment variable: process.env.JWT_SECRET
🟠 [HIGH] Missing Rate Limiting
No rate limiting on authentication attempts. Vulnerable to brute force attacks.
Implement: Use rate-limiting middleware (e.g., express-rate-limit)
🟡 [MEDIUM] Generic Error Message
Line 14: Could leak user existence information. Return generic error.
Review Modes: Tailored Analysis
Antigravity offers specialized review modes for different purposes. Choose the right mode based on your current focus.
Security Mode
antigravity review --mode security src/payment/checkout.tsFocuses exclusively on security vulnerabilities and best practices:
// src/services/payment.ts
async function processPayment(
cardDetails: CreditCard,
amount: number
): Promise<PaymentResult> {
// Security review checks:
// - PCI DSS compliance
// - Sensitive data exposure
// - Authentication validation
// - Input validation
// - API key management
const response = await fetch('https://api.payment.com/charge', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.STRIPE_SECRET}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
card: cardDetails, // Potential exposure
amount: amount
})
});
return response.json();
}Detected Issues:
- Never transmit full card details to external APIs
- Store only tokenized references
- Validate SSL/TLS certificate pinning
- Implement 3D Secure authentication
Performance Mode
antigravity review --mode performance src/database/queries.tsIdentifies efficiency bottlenecks and optimization opportunities:
# src/services/user_service.py
def get_user_dashboard_data(user_id: int) -> dict:
# Performance issues detected:
# - N+1 query pattern
# - Unbounded result sets
# - Missing database indexes
# - Inefficient joins
user = db.query('SELECT * FROM users WHERE id = ?', [user_id])
# Separate query for each data type
posts = db.query('SELECT * FROM posts WHERE user_id = ?', [user_id])
comments = []
for post in posts:
post_comments = db.query(
'SELECT * FROM comments WHERE post_id = ?',
[post.id] # Query runs for each post!
)
comments.extend(post_comments)
followers = db.query(
'SELECT * FROM followers WHERE following_id = ?',
[user_id]
)
return {
'user': user,
'posts': posts,
'comments': comments,
'followers': followers
}Optimization Suggestions:
def get_user_dashboard_data_optimized(user_id: int) -> dict:
# Single query with efficient joins
data = db.query('''
SELECT
u.*,
COALESCE(COUNT(DISTINCT p.id), 0) as post_count,
COALESCE(COUNT(DISTINCT c.id), 0) as comment_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
LEFT JOIN comments c ON c.post_id = p.id
WHERE u.id = ?
GROUP BY u.id
''', [user_id])
return data[0] # Single query instead of N+1Style & Quality Mode
antigravity review --mode style src/components/Dashboard.tsxEnforces consistent code style and maintainability standards:
// Before
export const DashboardComponent = ({ u, p, s, o, c }) => {
if (!u) return null;
return (
<div>
<h1>{u.nm}</h1>
<div>{p}</div>
{s && <span className={o}>{s}</span>}
{c && c()}
</div>
);
};
// Issues:
// - Abbreviated parameter names
// - Missing prop types
// - No documentation
// - Inconsistent return patternsAI Recommendations:
interface DashboardProps {
user: {
id: string;
name: string;
email: string;
};
posts: Post[];
status?: string;
statusClass?: string;
renderActions?: () => React.ReactNode;
}
/**
* Displays the user dashboard with posts and actions.
* @param user - Authenticated user object
* @param posts - User's posts to display
* @param status - Optional status message
* @param statusClass - CSS class for status styling
* @param renderActions - Optional action renderer
*/
export const DashboardComponent: React.FC<DashboardProps> = ({
user,
posts,
status,
statusClass,
renderActions
}) => {
if (!user) {
return null;
}
return (
<div className="dashboard">
<header>
<h1>{user.name}</h1>
</header>
<main className="posts-section">
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</main>
{status && (
<aside className={statusClass}>
{status}
</aside>
)}
{renderActions && (
<footer>
{renderActions()}
</footer>
)}
</div>
);
};Testing Mode
antigravity review --mode testing src/utils/validation.tsAnalyzes test coverage and suggests comprehensive test scenarios:
// src/utils/validation.ts
export function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
export function validatePhone(phone: string): boolean {
return /^\d{10}$/.test(phone);
}AI Testing Recommendations:
// src/utils/validation.test.ts
describe('Email Validation', () => {
describe('Valid emails', () => {
it('should accept standard email addresses', () => {
expect(validateEmail('user@example.com')).toBe(true);
});
it('should accept subdomains', () => {
expect(validateEmail('user@mail.company.co.uk')).toBe(true);
});
it('should accept numeric addresses', () => {
expect(validateEmail('123@456.com')).toBe(true);
});
});
describe('Invalid emails', () => {
it('should reject missing @ symbol', () => {
expect(validateEmail('invalidemail.com')).toBe(false);
});
it('should reject missing domain', () => {
expect(validateEmail('user@')).toBe(false);
});
it('should reject spaces', () => {
expect(validateEmail('user @example.com')).toBe(false);
});
});
describe('Edge cases', () => {
it('should reject empty string', () => {
expect(validateEmail('')).toBe(false);
});
it('should reject very long emails', () => {
const longEmail = 'a'.repeat(255) + '@example.com';
expect(validateEmail(longEmail)).toBe(false);
});
it('should handle internationalized domains', () => {
// Future enhancement consideration
expect(validateEmail('user@münchen.de')).toBe(true);
});
});
});Configuration & Customization
Project Review Configuration
Create .antigravity-review.yml at your project root to enforce consistent review standards:
# .antigravity-review.yml
review:
enabled: true
security:
severity_threshold: "warning"
check_sql_injection: true
check_xss: true
check_authentication: true
check_cryptography: true
check_file_upload: true
check_api_security: true
performance:
enabled: true
max_function_complexity: 12
max_cyclomatic_complexity: 10
detect_n_plus_one_queries: true
detect_memory_leaks: true
quality:
enabled: true
require_type_annotations: true
require_function_documentation: true
max_line_length: 100
enforce_naming_conventions: true
testing:
enabled: true
min_coverage_threshold: 80
require_edge_case_tests: true
# Patterns to exclude from review
exclude_patterns:
- "node_modules/**"
- "dist/**"
- "build/**"
- "*.test.ts"
- "*.spec.ts"
- "*.generated.ts"
# Automatic fixes
auto_fix:
enabled: true
fix_imports: true
format_code: true
remove_unused_variables: trueInline Review Comments
Suppress specific issues with explanatory comments:
// antigravity-ignore: security (legacy endpoint, migration planned Q3)
export function deprecatedAuthEndpoint() {
// ...
}
// antigravity-ignore: performance (small data set, optimization not worth it)
function niche_feature_with_inefficiency() {
// ...
}
// antigravity-ignore: style (matches legacy codebase for consistency)
const legacy_variable_naming = true;Integration with Pull Requests
Automated Review on PR Creation
Set up GitHub Actions to automatically review code when PRs are created:
# .github/workflows/ai-code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
paths:
- '**.ts'
- '**.tsx'
- '**.js'
- '**.jsx'
- '**.py'
jobs:
review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run Antigravity Code Review
uses: antigravity-lab/code-review-action@v2
with:
mode: comprehensive
severity_threshold: warning
post_comments: true
fail_on_critical: true
api_key: ${{ secrets.ANTIGRAVITY_API_KEY }}
- name: Post Review Summary
if: always()
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const review = JSON.parse(fs.readFileSync('./review-results.json'));
const comment = `
## 📊 AI Code Review Results
**Critical Issues:** ${review.critical.count}
**High Priority:** ${review.high.count}
**Warnings:** ${review.warnings.count}
${review.summary}
[View full report](${review.report_url})
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});Pre-Deployment Review Checks
Ensure all critical issues are resolved before production deployment:
#!/bin/bash
# scripts/pre-deploy-check.sh
set -e
echo "Running comprehensive code review..."
antigravity review \
--mode comprehensive \
--fail-on-critical \
--output json > review-report.json
CRITICAL_COUNT=$(jq '.critical.count' review-report.json)
if [ $CRITICAL_COUNT -gt 0 ]; then
echo "❌ Deployment blocked: $CRITICAL_COUNT critical issues found"
jq '.critical.issues[]' review-report.json
exit 1
fi
echo "✅ All critical issues resolved. Safe to deploy."
exit 0Real-World Review Example
Complex Payment Processing Function
// src/services/payments/processor.ts
import Stripe from 'stripe';
import { db } from '../../database';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createSubscription(
customerId: string,
planId: string,
billingEmail: string
) {
try {
// Create Stripe customer
const customer = await stripe.customers.create({
email: billingEmail,
metadata: { internal_id: customerId }
});
// Create subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: planId }]
});
// Save to database
await db.query(
'INSERT INTO subscriptions (customer_id, stripe_customer_id, plan_id, status) VALUES (?, ?, ?, ?)',
[customerId, customer.id, planId, subscription.status]
);
// Send confirmation email
await sendConfirmationEmail(billingEmail, subscription.id);
return {
success: true,
subscriptionId: subscription.id
};
} catch (error) {
console.log(error);
throw error;
}
}AI Code Review Findings:
🔴 [CRITICAL] Unvalidated Customer Email
Line 15: billingEmail is not validated before passing to Stripe API.
Risk: Invalid emails could cause payment processing failures.
Fix: Validate email format before using.
🔴 [CRITICAL] Incomplete Error Handling
Lines 35-37: Generic error handling doesn't distinguish between
Stripe errors, database errors, and email service failures.
Fix: Implement specific error handling for each service.
🟠 [HIGH] Stripe Event Webhook Required
Line 24: Subscription creation is acknowledged without webhook
confirmation. If webhook fails, database becomes inconsistent.
Risk: Orphaned records or missing subscription updates.
🟠 [HIGH] Race Condition Risk
Lines 23-28: Database insertion happens after Stripe confirmation.
If insertion fails, Stripe has the subscription but DB doesn't.
Fix: Use idempotency keys and transaction management.
🟡 [MEDIUM] Missing Retry Logic
Line 19: Email could fail silently. No retry mechanism.
Fix: Implement queue-based email delivery with retries.
🟡 [MEDIUM] PCI DSS Compliance
Line 15: Ensure no card data is being logged or stored.
Verify: All sensitive data flows through tokenization.
Best Practices
Effective Review Workflow
- Continuous Integration — Run reviews on every commit
- Progressive Severity — Fix critical issues first, then warnings
- Team Alignment — Customize
.antigravity-review.ymlto match team standards - Learning Orientation — Use detected issues as teaching opportunities
- False Positive Management — Carefully curate exclusion rules
What Not to Do
// ❌ Ignoring all warnings
// antigravity-ignore
function needsRefactoring() { }
// ✅ Targeted, justified exclusions
// antigravity-ignore: performance (accepted complexity for readability)
function complexBusinessLogic() { }Where to start
Trying to switch on every check at once buries you under false positives, and the habit never sticks. What worked for me was enabling Security Mode alone and clearing only the Critical findings first. Once those standards started to feel natural, I layered in performance and readability checks one at a time.
As a concrete next step, drop a .antigravity-review.yml with just the security rules into one of your repositories and run a single review against your most recent commit. The moment you see the first finding on your own code, you will have a feel for where this tool fits.