Gemini CLI is Google's command-line interface for interacting with Gemini models (Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 3 Pro) for code generation, analysis, and AI-assisted development. Released in late 2024, it provides:
- Terminal-native workflow: No IDE required
- Project context awareness: Automatic codebase indexing
- Multi-modal capabilities: Code, images, and text understanding
- High token limits: Up to 1M context window (Gemini 3 Pro)
- Google Cloud integration: Native GCP authentication
Why Project-Specific Rules Matter
Unlike general-purpose chat interfaces, Gemini CLI with project rules:
✅ Enforces coding standards automatically
✅ Maintains consistency across AI-generated code
✅ Reduces review cycles by 40-60%
✅ Accelerates onboarding for new team members
✅ Preserves institutional knowledge in version-controlled files
Gemini CLI's Unique Approach
| Aspect | Gemini CLI Approach | Key Difference |
|---|---|---|
| Rules Location |
.gemini/instructions.md (project) or ~/.gemini/instructions.md (global) |
Google-specific naming convention |
| Format | Pure Markdown (no YAML frontmatter) | Simplified, no metadata |
| Loading | Auto-loads on gemini chat start |
Seamless integration |
| Scope | Project-wide or user-wide | Two-tier hierarchy |
| Model Selection | Via CLI flags or config file | Flexible per-session |
2. Installation & Setup
Prerequisites
Required:
- Python 3.8+ or Node.js 18+
- Google Cloud account with Gemini API access
- API key or Application Default Credentials (ADC)
Optional:
- Git (for version control of rules)
- A text editor (VS Code, Vim, nano)
Installation Methods
Method 1: Python (pip)
# Install via pip
pip install google-generativeai-cli
# Verify installation
gemini --version
# Expected: gemini-cli 2.0.x
# Check available commands
gemini --help
Method 2: Node.js (npm)
# Install globally via npm
npm install -g @google/generative-ai-cli
# Verify installation
gemini --version
# Alternative: Use without installing
npx @google/generative-ai-cli chat
Method 3: From Source (Development)
# Clone repository
git clone https://github.com/google/generative-ai-cli.git
cd generative-ai-cli
# Install dependencies
npm install
# or
pip install -e .
# Run from source
./bin/gemini --version
Authentication Setup
Option A: API Key (Quickest)
# Set environment variable
export GEMINI_API_KEY="your-api-key-here"
# Make persistent (add to ~/.bashrc or ~/.zshrc)
echo 'export GEMINI_API_KEY="your-api-key-here"' >> ~/.zshrc
source ~/.zshrc
# Test authentication
gemini chat --message "Hello, test authentication"
Option B: Google Cloud ADC (Recommended for Teams)
# Install Google Cloud SDK
curl https://sdk.cloud.google.com | bash
# Initialize and authenticate
gcloud init
gcloud auth application-default login
# Set project
gcloud config set project YOUR_PROJECT_ID
# Gemini CLI automatically uses ADC
gemini chat --message "Test ADC authentication"
Option C: Service Account (CI/CD)
# Download service account JSON key
# (from Google Cloud Console → IAM → Service Accounts)
# Set environment variable
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
# Verify
gemini chat --message "Test service account auth"
First Run Configuration
# Initialize configuration
gemini init
# This creates:
# ~/.gemini/
# ├── config.yaml # Global settings
# └── instructions.md # Global instructions (optional)
# View configuration
cat ~/.gemini/config.yaml
3. Configuration Architecture
Directory Structure Overview
# Global Configuration (User-level)
~/.gemini/
├── config.yaml # Model settings, preferences
├── instructions.md # Global coding rules (optional)
├── history/ # Chat history
│ ├── session-001.json
│ └── session-002.json
└── cache/ # Context cache
# Project Configuration (Repository-level)
my-project/
├── .gemini/
│ ├── instructions.md # Project-specific rules ⭐
│ ├── context.yaml # Context configuration (optional)
│ └── .geminiignore # Files to exclude
├── src/
└── docs/
Configuration Loading Priority
Gemini CLI loads configuration in this order (later overrides earlier):
- Built-in defaults (Gemini's base behavior)
-
Global config (
~/.gemini/config.yaml) -
Global instructions (
~/.gemini/instructions.md) -
Project config (
.gemini/context.yaml) -
Project instructions (
.gemini/instructions.md) ⭐ Highest Priority -
CLI flags (
--model,--temperature, etc.)
File Responsibilities
| File | Purpose | Scope | Format |
|---|---|---|---|
~/.gemini/config.yaml |
Model selection, API settings, defaults | User (all projects) | YAML |
~/.gemini/instructions.md |
Personal coding style, preferences | User (all projects) | Markdown |
.gemini/instructions.md |
Project coding standards, architecture | Project (team-shared) | Markdown |
.gemini/context.yaml |
Context window settings, file inclusion | Project | YAML |
.geminiignore |
Files/directories to exclude from context | Project | Gitignore syntax |
4. The .gemini/instructions.md System
Overview
.gemini/instructions.md is the primary mechanism for encoding project-specific rules in Gemini CLI. It's a plain Markdown file that Gemini automatically loads at the start of every chat session.
File Location
Project-Level (Recommended for Teams):
my-project/.gemini/instructions.md
Global User-Level:
~/.gemini/instructions.md
How It Works
Loading Mechanism:
- User runs
gemini chatin project directory - Gemini CLI searches for
.gemini/instructions.mdin current directory - If found, entire file content prepended to system prompt
- If not found, searches for
~/.gemini/instructions.md - Instructions remain active for entire chat session
Key Characteristics:
✅ Automatic: No manual loading required
✅ Persistent: Active for all messages in session
✅ Hierarchical: Project overrides global
✅ Version-controlled: Commit to git for team sharing
✅ No validation: Pure Markdown, no schema enforcement
Basic Structure
# Project Instructions for Gemini CLI
## Project Context
Brief description of what this project does and its technology stack.
## Coding Standards
Key rules for code generation and modification.
## Architecture
How the codebase is organized and structured.
## Testing Requirements
Testing expectations and patterns.
## Important Notes
Critical information, gotchas, and constraints.
Minimal Working Example
Create the file:
# Navigate to your project
cd my-project
# Create .gemini directory
mkdir -p .gemini
# Create instructions file
cat > .gemini/instructions.md << 'EOF'
# Project Instructions
## Tech Stack
- Language: Python 3.11
- Framework: FastAPI
- Database: PostgreSQL with SQLAlchemy
## Coding Standards
- Use type hints for all functions
- Follow PEP 8 style guide
- Prefer async/await over threading
## Testing
- Use pytest for all tests
- Minimum 80% code coverage required
## Important
- Never commit secrets to git
- Always validate user inputs with Pydantic
EOF
Test it:
# Start chat session
gemini chat
# Instructions are automatically loaded
# Try a prompt to verify:
> "Create a new API endpoint for user registration"
# Gemini should follow your standards (type hints, Pydantic validation, etc.)
5. Configuration File Format Reference
config.yaml Schema
Location: ~/.gemini/config.yaml
Complete Schema:
#############################################
# GEMINI CLI GLOBAL CONFIGURATION
# Location: ~/.gemini/config.yaml
#############################################
# Default model selection
model: gemini-3-pro-preview
# Options:
# - gemini-3-pro-preview # Best quality, 1M context
# - gemini-2.5-flash # Fast, 1M context
# - gemini-2.5-flash-lite # Experimental, newest features
# - gemini-2.5-pro # Preview of next generation
# API Configuration
api:
key: null # API key (prefer env var GEMINI_API_KEY)
endpoint: https://generativelanguage.googleapis.com
timeout: 60 # Request timeout in seconds
retry: 3 # Number of retries on failure
# Generation Parameters
generation:
temperature: 0.7 # Creativity (0.0-2.0)
top_p: 0.95 # Nucleus sampling
top_k: 40 # Top-k sampling
max_output_tokens: 8192 # Max response length
# Safety Settings
safety:
harassment: BLOCK_MEDIUM_AND_ABOVE
hate_speech: BLOCK_MEDIUM_AND_ABOVE
sexually_explicit: BLOCK_MEDIUM_AND_ABOVE
dangerous_content: BLOCK_MEDIUM_AND_ABOVE
# Options: BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE
# Context Settings
context:
auto_include_files: true # Automatically include relevant files
max_context_tokens: 1000000 # Max tokens for context (model-dependent)
index_codebase: true # Build semantic index of codebase
# Chat Settings
chat:
history_length: 50 # Number of messages to keep in history
save_history: true # Save chat history to ~/.gemini/history/
stream_response: true # Stream responses (vs. wait for complete)
syntax_highlighting: true # Syntax highlight code in terminal
# Output Settings
output:
format: markdown # Options: markdown, plain, json
color_scheme: auto # Options: auto, light, dark, none
verbose: false # Show detailed logging
show_token_count: false # Display token usage per request
# File Handling
files:
ignore_file: .geminiignore # File with ignore patterns
auto_gitignore: true # Respect .gitignore patterns
include_hidden: false # Include hidden files (.*) in context
max_file_size: 1048576 # Max file size to include (1MB)
# Experimental Features
experimental:
multi_modal: true # Enable image understanding
function_calling: false # Enable function/tool calling (beta)
code_execution: false # Allow code execution (dangerous!)
.gemini/context.yaml Schema
Location: .gemini/context.yaml (project-level)
Purpose: Fine-tune context inclusion for specific projects.
#############################################
# PROJECT CONTEXT CONFIGURATION
# Location: .gemini/context.yaml
#############################################
# Context Strategy
strategy: smart # Options: smart, explicit, minimal, full
# smart: AI decides what to include
# explicit: Only include specified files
# minimal: Only currently edited file
# full: Include entire codebase (use with caution)
# File Inclusion (for 'explicit' strategy)
include:
patterns:
- "src/**/*.py" # All Python files in src/
- "tests/**/*.py" # All test files
- "*.md" # Root-level markdown files
- "pyproject.toml" # Config files
- "requirements.txt"
files:
- "src/main.py" # Specific files
- "docs/architecture.md"
- "README.md"
# File Exclusion
exclude:
patterns:
- "**/__pycache__/**"
- "**/*.pyc"
- ".venv/**"
- "node_modules/**"
- "build/**"
- "dist/**"
files:
- "secrets.py" # Never include
- ".env"
# Directory Weights (for 'smart' strategy)
# Higher weight = more likely to include
weights:
"src/": 1.0 # Normal priority
"tests/": 0.7 # Lower priority
"docs/": 0.5 # Even lower
"scripts/": 0.3 # Rarely needed
# Context Budget
budget:
max_tokens: 500000 # Max tokens for file context
reserve_tokens: 100000 # Reserve for conversation
# Semantic Index
index:
enabled: true # Build semantic search index
update_frequency: on_change # Options: on_change, daily, manual
embeddings_model: text-embedding-004
# Advanced
cache:
enabled: true # Cache context between sessions
ttl: 3600 # Cache time-to-live (seconds)
.geminiignore Syntax
Location: .gemini/.geminiignore
Purpose: Exclude files from context (same syntax as .gitignore).
# .gemini/.geminiignore
# Dependencies
node_modules/
venv/
.venv/
__pycache__/
*.pyc
# Build outputs
dist/
build/
out/
.next/
target/
# IDE files
.vscode/
.idea/
*.swp
.DS_Store
# Sensitive files
.env
.env.local
*.key
secrets.yaml
# Large data files
*.csv
*.json.gz
data/
datasets/
# Generated code
src/generated/
*.pb.go
*_pb2.py
# Logs
*.log
logs/
# Media files (if not needed)
*.mp4
*.mov
*.png
*.jpg
# Documentation builds
docs/_build/
site/
6. Project-Level Rules
Complete .gemini/instructions.md Template
This is the definitive template for project-specific rules:
# Gemini CLI Instructions for [Project Name]
**Version**: 1.0.0
**Last Updated**: 2025-11-29
**Maintainer**: [Team/Person]
---
## PROJECT OVERVIEW
**Purpose**: [What this project does in 1-2 sentences]
**Example**:
> RESTful API for e-commerce platform built with FastAPI and PostgreSQL.
> Handles user authentication, product catalog, orders, and payments.
**Key Goals**:
- [Primary goal, e.g., "Support 10k concurrent users"]
- [Secondary goal, e.g., "Sub-100ms API response times"]
- [Tertiary goal, e.g., "99.9% uptime"]
---
## TECHNOLOGY STACK
### Backend
- **Language**: Python 3.11
- **Framework**: FastAPI 0.104
- **Database**: PostgreSQL 16 with SQLAlchemy 2.0
- **ORM**: SQLAlchemy (async)
- **Validation**: Pydantic 2.5
- **Authentication**: JWT with python-jose
- **Testing**: pytest 7.4, pytest-asyncio
### Infrastructure
- **Containerization**: Docker, docker-compose
- **Hosting**: Google Cloud Run
- **CI/CD**: GitHub Actions
- **Monitoring**: Google Cloud Monitoring
### Key Libraries
- `asyncpg`: Async PostgreSQL driver
- `redis`: Caching layer
- `httpx`: Async HTTP client
- `python-multipart`: File upload handling
---
## PROJECT STRUCTURE
project/
├── app/
│ ├── api/ # API routes
│ │ ├── v1/ # API version 1
│ │ │ ├── endpoints/ # Endpoint modules
│ │ │ └── deps.py # Dependencies (DB session, auth)
│ │ └── router.py # Main API router
│ ├── core/ # Core functionality
│ │ ├── config.py # Settings (from env vars)
│ │ ├── security.py # Auth utilities
│ │ └── database.py # DB connection
│ ├── models/ # SQLAlchemy models
│ ├── schemas/ # Pydantic schemas
│ ├── services/ # Business logic layer
│ ├── repositories/ # Data access layer
│ └── main.py # FastAPI app entry point
├── tests/
│ ├── unit/ # Unit tests
│ ├── integration/ # API integration tests
│ └── conftest.py # Pytest fixtures
├── migrations/ # Alembic migrations
└── scripts/ # Utility scripts
**Key Directories**:
- `app/api/v1/endpoints/`: One file per resource (users.py, products.py)
- `app/models/`: SQLAlchemy ORM models
- `app/schemas/`: Pydantic models for request/response validation
- `app/services/`: Business logic (never in endpoints)
- `app/repositories/`: Database queries (abstraction layer)
---
## CODING STANDARDS
### Python Style
#### Type Hints (REQUIRED)
ALL functions must have complete type hints:
python
✅ CORRECT
from typing import Optional
async def get_user(
user_id: int,
db: AsyncSession
) -> Optional[User]:
result = await db.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
❌ WRONG - No type hints
async def get_user(user_id, db):
result = await db.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
#### Async/Await Pattern
python
✅ CORRECT - Async for I/O operations
async def create_order(order_data: OrderCreate, db: AsyncSession) -> Order:
order = Order(**order_data.dict())
db.add(order)
await db.commit()
await db.refresh(order)
return order
❌ WRONG - Sync for I/O
def create_order(order_data: OrderCreate, db: Session) -> Order:
order = Order(**order_data.dict())
db.add(order)
db.commit()
db.refresh(order)
return order
#### Error Handling
python
✅ CORRECT - Specific exceptions with context
from fastapi import HTTPException, status
async def delete_user(user_id: int, db: AsyncSession) -> None:
result = await db.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with id {user_id} not found"
)
await db.delete(user)
await db.commit()
❌ WRONG - Generic exception
async def delete_user(user_id: int, db: AsyncSession) -> None:
user = await db.get(User, user_id)
if not user:
raise Exception("Not found") # Too generic!
await db.delete(user)
await db.commit()
### API Endpoint Structure
**Standard Endpoint Pattern**:
python
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.deps import get_db, get_current_user
from app.schemas.user import UserCreate, UserResponse
from app.services.user_service import UserService
router = APIRouter()
@router.post(
"/users",
response_model=UserResponse,
status_code=status.HTTP_201_CREATED,
summary="Create a new user",
description="Creates a new user account with the provided details"
)
async def create_user(
user_data: UserCreate,
db: AsyncSession = Depends(get_db)
) -> UserResponse:
"""
Create a new user.
Args:
user_data: User registration data
db: Database session
Returns:
Created user data
Raises:
HTTPException: 409 if email already exists
"""
service = UserService(db)
# Check if user exists
existing = await service.get_by_email(user_data.email)
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Email already registered"
)
# Create user
user = await service.create(user_data)
return UserResponse.from_orm(user)
**Key Requirements**:
1. Router with proper prefix
2. Response model defined
3. Status code specified
4. Summary and description for OpenAPI docs
5. Docstring with Args/Returns/Raises
6. Business logic in service layer
7. Specific HTTP exceptions with detail
### Pydantic Schemas
python
from pydantic import BaseModel, EmailStr, Field, validator
from datetime import datetime
from typing import Optional
class UserBase(BaseModel):
"""Base user schema with common fields."""
email: EmailStr = Field(..., description="User email address")
full_name: str = Field(..., min_length=2, max_length=100)
class UserCreate(UserBase):
"""Schema for user creation (includes password)."""
password: str = Field(..., min_length=12, max_length=100)
@validator('password')
def validate_password(cls, v: str) -> str:
if not any(char.isdigit() for char in v):
raise ValueError('Password must contain at least one digit')
if not any(char.isupper() for char in v):
raise ValueError('Password must contain at least one uppercase letter')
return v
class UserResponse(UserBase):
"""Schema for user responses (no password)."""
id: int
is_active: bool
created_at: datetime
class Config:
orm_mode = True
from_attributes = True # Pydantic v2
### Database Patterns
#### Repository Pattern
python
from typing import Optional, List
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.schemas.user import UserCreate
class UserRepository:
"""Data access layer for User model."""
def __init__(self, db: AsyncSession):
self.db = db
async def get_by_id(self, user_id: int) -> Optional[User]:
"""Fetch user by ID."""
result = await self.db.execute(
select(User).where(User.id == user_id)
)
return result.scalar_one_or_none()
async def get_by_email(self, email: str) -> Optional[User]:
"""Fetch user by email."""
result = await self.db.execute(
select(User).where(User.email == email)
)
return result.scalar_one_or_none()
async def create(self, user_data: UserCreate) -> User:
"""Create new user."""
user = User(**user_data.dict(exclude={'password'}))
user.hashed_password = get_password_hash(user_data.password)
self.db.add(user)
await self.db.commit()
await self.db.refresh(user)
return user
async def list(
self,
skip: int = 0,
limit: int = 100
) -> List[User]:
"""List users with pagination."""
result = await self.db.execute(
select(User)
.offset(skip)
.limit(limit)
)
return result.scalars().all()
---
## TESTING REQUIREMENTS
### Test Structure
python
import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.schemas.user import UserCreate
@pytest.mark.asyncio
async def test_create_user_success(
client: AsyncClient,
db: AsyncSession
):
"""Test successful user creation."""
# ARRANGE
user_data = {
"email": "test@example.com",
"full_name": "Test User",
"password": "SecurePass123"
}
# ACT
response = await client.post("/api/v1/users", json=user_data)
# ASSERT
assert response.status_code == 201
data = response.json()
assert data["email"] == user_data["email"]
assert data["full_name"] == user_data["full_name"]
assert "password" not in data # Never return password
assert "id" in data
@pytest.mark.asyncio
async def test_create_user_duplicate_email(
client: AsyncClient,
db: AsyncSession
):
"""Test user creation with existing email fails."""
# ARRANGE
user_data = {
"email": "duplicate@example.com",
"full_name": "First User",
"password": "SecurePass123"
}
# Create first user
await client.post("/api/v1/users", json=user_data)
# ACT - Try to create second user with same email
response = await client.post("/api/v1/users", json=user_data)
# ASSERT
assert response.status_code == 409
assert "already registered" in response.json()["detail"].lower()
### Coverage Requirements
- **Unit Tests**: 90% minimum (business logic, services, repositories)
- **Integration Tests**: 80% minimum (API endpoints)
- **E2E Tests**: Critical user flows only
### Test Commands
bash
Run all tests
pytest
Run with coverage
pytest --cov=app --cov-report=html
Run specific test file
pytest tests/unit/test_users.py
Run specific test
pytest tests/unit/test_users.py::test_create_user_success
Run only unit tests
pytest tests/unit/
Run with verbose output
pytest -v
---
## SECURITY REQUIREMENTS
### Authentication Pattern
python
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import jwt, JWTError
from app.core.config import settings
from app.schemas.auth import TokenPayload
security = HTTPBearer()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: AsyncSession = Depends(get_db)
) -> User:
"""
Validate JWT token and return current user.
Raises:
HTTPException: 401 if token invalid or user not found
"""
try:
payload = jwt.decode(
credentials.credentials,
settings.SECRET_KEY,
algorithms=[settings.ALGORITHM]
)
token_data = TokenPayload(**payload)
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials"
)
user = await UserRepository(db).get_by_id(token_data.sub)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return user
### Input Validation (ALWAYS)
python
✅ CORRECT - Pydantic validates everything
from pydantic import BaseModel, Field, validator
class ProductCreate(BaseModel):
name: str = Field(..., min_length=3, max_length=200)
price: float = Field(..., gt=0, le=1000000)
quantity: int = Field(..., ge=0)
@validator('name')
def validate_name(cls, v):
if not v.strip():
raise ValueError('Name cannot be only whitespace')
return v.strip()
❌ WRONG - No validation
@router.post("/products")
async def create_product(data: dict): # dict has no validation!
product = Product(**data) # Dangerous!
### Never Hardcode Secrets
python
✅ CORRECT - Use environment variables
from pydantic import BaseSettings
class Settings(BaseSettings):
SECRET_KEY: str
DATABASE_URL: str
REDIS_URL: str
class Config:
env_file = ".env"
settings = Settings()
❌ WRONG - Hardcoded secrets
SECRET_KEY = "my-secret-key-123" # NEVER DO THIS
DATABASE_URL = "postgresql://user:pass@localhost/db" # NEVER
---
## COMMON COMMANDS
### Development
bash
Start development server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Run with docker-compose
docker-compose up -d
View logs
docker-compose logs -f api
### Database
bash
Create migration
alembic revision --autogenerate -m "Add users table"
Apply migrations
alembic upgrade head
Rollback one migration
alembic downgrade -1
View migration history
alembic history
### Testing
bash
Run tests
pytest
With coverage
pytest --cov=app --cov-report=term-missing
Run linters
ruff check app/
mypy app/
### Deployment
bash
Build Docker image
docker build -t my-api:latest .
Push to Google Container Registry
docker tag my-api:latest gcr.io/PROJECT_ID/my-api:latest
docker push gcr.io/PROJECT_ID/my-api:latest
Deploy to Cloud Run
gcloud run deploy my-api \
--image gcr.io/PROJECT_ID/my-api:latest \
--platform managed \
--region us-central1
---
## KNOWN ISSUES & GOTCHAS
### Issue 1: SQLAlchemy Async Session Management
**Symptom**: "Object is already attached to session" errors
**Cause**: Mixing sync and async SQLAlchemy operations
**Solution**:
python
✅ CORRECT - Use async session consistently
async with get_async_session() as session:
user = await session.get(User, user_id)
user.name = "Updated"
await session.commit()
❌ WRONG - Mixing session types
session = Session() # Sync session
user = await session.get(User, user_id) # Won't work!
### Issue 2: Pydantic v2 Migration
**Symptom**: `orm_mode` not working
**Solution**: Use `from_attributes = True` in Pydantic v2
python
class UserResponse(BaseModel):
# Pydantic v2
class Config:
from_attributes = True # Not orm_mode!
### Issue 3: FastAPI Dependency Injection Order
**Symptom**: Dependencies not executing in expected order
**Solution**: Order matters in Depends() - auth first, then DB
python
✅ CORRECT - Auth dependency before DB
@router.get("/protected")
async def protected_route(
current_user: User = Depends(get_current_user), # First
db: AsyncSession = Depends(get_db) # Second
):
pass
---
## IMPORTANT NOTES
### API Standards
- **Versioning**: All routes prefixed with `/api/v1/`
- **Status Codes**: Use appropriate HTTP status codes (200, 201, 400, 401, 404, 409, 500)
- **Response Format**: Always return Pydantic models (auto-serialized to JSON)
- **Pagination**: Use `skip` and `limit` query parameters (default limit=100, max=1000)
### Database Conventions
- **Naming**: Snake_case for table/column names
- **Timestamps**: Always include `created_at` and `updated_at` (auto-managed)
- **Soft Deletes**: Use `is_deleted` flag instead of hard deletes
- **Indexes**: Add indexes for frequently queried columns
### Authentication
- **JWT Expiration**: Access tokens expire in 30 minutes
- **Refresh Tokens**: Valid for 7 days, stored in Redis
- **Password Hashing**: Use bcrypt (never plain text, never MD5)
### Performance
- **Caching**: Redis for frequently accessed data (TTL: 5 minutes)
- **Connection Pooling**: Max 20 DB connections
- **Query Optimization**: Always use `select()` with specific columns, avoid `SELECT *`
---
## DEPLOYMENT CHECKLIST
Before deploying:
- [ ] All tests pass: `pytest --cov=app`
- [ ] Type checks pass: `mypy app/`
- [ ] Linter passes: `ruff check app/`
- [ ] Database migrations applied: `alembic upgrade head`
- [ ] Environment variables set in Cloud Run
- [ ] Secrets rotated (if compromised)
- [ ] API documentation reviewed: `/docs` endpoint
- [ ] Load testing completed (target: 1000 RPS)
---
## WORKING WITH GEMINI CLI
### Effective Prompts
- **Be specific**: "Add a new POST endpoint for creating products following the repository pattern"
- **Reference patterns**: "Use the same authentication pattern as in users.py"
- **Request tests**: "Also generate pytest tests with >90% coverage"
### Context Tips
- Gemini auto-loads this file on `gemini chat` start
- Use `@filename` to reference specific files
- For architecture questions, ask: "Explain the repository pattern used in this project"
---
*This instructions file is version-controlled. Update as the project evolves.*
7. Global vs. Project Configuration
When to Use Global Instructions
Global (~/.gemini/instructions.md):
✅ Personal Coding Style
# My Personal Coding Preferences
## Response Format
- Give concise answers, no unnecessary explanations
- Show code first, explanation after
- Use modern ES2022+ JavaScript
## Code Style
- I prefer functional programming patterns
- Use destructuring wherever readable
- Prefer `const` over `let`, never `var`
## Workflow
- I use Vim keybindings
- I prefer CLI tools over GUIs
- Dark mode always
✅ Cross-Project Standards
# Universal Standards (All My Projects)
## Git Commits
- Use Conventional Commits format
- Types: feat, fix, docs, style, refactor, test, chore
## Documentation
- All public functions need JSDoc/docstrings
- README must have: Installation, Usage, Examples
## Security
- Never log sensitive data
- Validate all user inputs
- Use parameterized queries (no string concatenation)
When to Use Project Instructions
Project (.gemini/instructions.md):
✅ Project-Specific Standards
✅ Technology Stack Details
✅ Architecture Patterns
✅ Team Conventions
✅ Known Issues & Workarounds
Combining Both
Hierarchy:
- Built-in Gemini defaults
- Global instructions (
~/.gemini/instructions.md) -
Project instructions (
.gemini/instructions.md) ← Highest priority
Example Combination:
Global (~/.gemini/instructions.md):
# Global Rules
I prefer:
- Concise responses
- Functional programming
- TypeScript over JavaScript
Project (.gemini/instructions.md):
# Project Rules
Tech Stack:
- Language: TypeScript 5.4 (strict mode)
- Framework: React 19 + Next.js 15
- State: Zustand (NOT Redux)
Note: This project uses Zustand for state management,
which overrides any general preferences about Redux.
Result: Gemini applies both, with project rules taking precedence when they conflict.
8. Advanced Rule Patterns
Conditional Rules by File Type
## Language-Specific Rules
### When Working with Python Files (*.py)
- Use type hints for all functions
- Follow PEP 8 style guide
- Prefer dataclasses over plain classes for data containers
- Use `pathlib` instead of `os.path`
### When Working with TypeScript Files (*.ts, *.tsx)
- Enable strict mode in tsconfig.json
- Explicit return types on all functions
- Prefer `unknown` over `any`
- Use discriminated unions for variants
### When Working with SQL Files (*.sql)
- Use uppercase for SQL keywords (SELECT, FROM, WHERE)
- One clause per line for readability
- Always use parameterized queries in code (never string concatenation)
### When Working with YAML Files (*.yaml, *.yml)
- 2-space indentation (never tabs)
- Quote strings with special characters
- Validate with yamllint before committing
Hierarchical Complexity Rules
## Progressive Complexity Guidelines
### Level 1: Simple Tasks (Quick Fixes, Small Changes)
- Make minimal changes
- Preserve existing patterns
- Don't refactor unless asked
- Single file modifications preferred
### Level 2: Medium Tasks (New Features, Refactoring)
- Plan changes before implementing
- Show architectural diagram if >3 files affected
- Write tests alongside implementation
- Update documentation
### Level 3: Complex Tasks (System Design, Major Refactors)
- MUST create Architecture Decision Record (ADR) first
- Present 2-3 solution options with trade-offs
- Break into phases (planning → implementation → testing)
- Request review before proceeding to next phase
Context-Aware Examples
## API Endpoint Creation Pattern
When asked to create a new API endpoint, follow this exact sequence:
### Step 1: Define Pydantic Schemas
python
schemas/product.py
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: str = Field(..., min_length=3)
price: float = Field(..., gt=0)
class ProductResponse(ProductCreate):
id: int
created_at: datetime
class Config:
from_attributes = True
### Step 2: Create Repository Method
python
repositories/product_repository.py
async def create(self, data: ProductCreate) -> Product:
product = Product(**data.dict())
self.db.add(product)
await self.db.commit()
await self.db.refresh(product)
return product
### Step 3: Add Service Layer
python
services/product_service.py
async def create_product(
self,
data: ProductCreate
) -> Product:
# Business logic here
return await self.repository.create(data)
### Step 4: Create Endpoint
python
api/v1/endpoints/products.py
@router.post(
"/products",
response_model=ProductResponse,
status_code=201
)
async def create_product(
data: ProductCreate,
db: AsyncSession = Depends(get_db)
):
service = ProductService(db)
return await service.create_product(data)
### Step 5: Write Tests
python
tests/test_products.py
async def test_create_product_success(client, db):
response = await client.post(
"/api/v1/products",
json={"name": "Test Product", "price": 9.99}
)
assert response.status_code == 201
assert response.json()["name"] == "Test Product"
ALWAYS follow this order. Don't skip steps.
Anti-Pattern Prevention
## Code Anti-Patterns to AVOID
### ❌ FORBIDDEN Pattern 1: Hardcoded Configuration
python
NEVER DO THIS
DATABASE_URL = "postgresql://user:password@localhost/db"
API_KEY = "sk-1234567890abcdef"
MAX_RETRIES = 5 # Use config/env var instead
**Why it's wrong**:
- Security risk (secrets in code)
- Inflexible (requires code changes for config)
- Environment-specific values
**✅ CORRECT Alternative**:
python
from pydantic import BaseSettings
class Settings(BaseSettings):
database_url: str
api_key: str
max_retries: int = 5
class Config:
env_file = ".env"
settings = Settings()
---
### ❌ FORBIDDEN Pattern 2: Swallowing Exceptions
python
NEVER DO THIS
try:
result = await dangerous_operation()
except:
pass # Silent failure!
**Why it's wrong**:
- Masks bugs
- No debugging information
- Unexpected behavior
**✅ CORRECT Alternative**:
python
import logging
logger = logging.getLogger(name)
try:
result = await dangerous_operation()
except SpecificException as e:
logger.error(f"Operation failed: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail="Operation failed"
)
---
### ❌ FORBIDDEN Pattern 3: Direct ORM in Endpoints
python
NEVER DO THIS
@router.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User))
return result.scalars().all() # ORM in endpoint!
**Why it's wrong**:
- Violates separation of concerns
- Hard to test
- No business logic layer
**✅ CORRECT Alternative**:
python
@router.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
service = UserService(db)
return await service.list_users() # Service layer
9. Model Configuration
Available Gemini Models
| Model | Context Window | Best For | Speed | Cost |
|---|---|---|---|---|
gemini-3-pro-preview |
1M tokens | Complex reasoning, large codebases | Slow | $$$ |
gemini-2.5-flash |
1M tokens | Balanced performance | Fast | $$ |
gemini-2.5-flash-lite |
1M tokens | Experimental features, newest | Fast | $$ |
gemini-2.5-pro |
1M tokens | Preview of next generation | Medium | $$$ |
Selecting Models
Method 1: Global Default (config.yaml)
# ~/.gemini/config.yaml
model: gemini-2.5-flash
Method 2: Per-Session (CLI Flag)
# Use specific model for this session
gemini chat --model gemini-3-pro-preview
# Use Flash for quick questions
gemini chat --model gemini-2.5-flash
Method 3: Environment Variable
export GEMINI_MODEL="gemini-2.5-flash-lite"
gemini chat
Model-Specific Parameters
# ~/.gemini/config.yaml
generation:
temperature: 0.7 # Creativity (0.0 = deterministic, 2.0 = very creative)
top_p: 0.95 # Nucleus sampling (0.0-1.0)
top_k: 40 # Top-k sampling
max_output_tokens: 8192 # Response length limit
# Model-specific overrides
model_overrides:
gemini-3-pro-preview:
temperature: 0.5 # More deterministic for production code
max_output_tokens: 16384
gemini-2.5-flash-lite:
temperature: 0.9 # More creative for brainstorming
top_k: 50
Choosing the Right Model
Use Gemini 3 Pro when:
- Working with large codebases (>50 files)
- Refactoring complex logic
- Analyzing architectural patterns
- Debugging subtle race conditions
Use Gemini 2.5 Flash when:
- Generating boilerplate code
- Writing unit tests
- Explaining code snippets
- Quick fixes or small changes
- Cost is a concern
Use Gemini 2.5 Flash Lite when:
- Trying basi features
- Needing the fastest possible responses
- Working on non-critical code
10. Context Management
How Gemini CLI Handles Context
Gemini CLI uses a semantic index to retrieve relevant code snippets based on your query, combined with an active context window for currently open or referenced files.
The Semantic Index
When you first run gemini chat in a project, it:
- Scans all non-ignored files
- Chunks code into logical blocks
- Generates embeddings
- Stores index in
.gemini/index/
To force reindexing:
gemini index --rebuild
Manual Context Management
You can explicitly add files to the context:
# Add specific files
gemini chat --context src/main.py src/utils.py
# Add directory
gemini chat --context src/models/
Inside a chat session:
> /add src/services/user_service.py
> /remove src/main.py
> /clear (resets context)
Context Strategy Configuration
In .gemini/context.yaml:
# Smart strategy (default): Uses semantic search + recent files
strategy: smart
# Explicit strategy: Only files you manually add
# strategy: explicit
# Full strategy: Dumps everything into context (up to limit)
# strategy: full
# File weights for smart strategy
weights:
"src/core/": 2.5 # High priority (core logic)
"tests/": 0.5 # Low priority
"docs/": 0.2 # Very low priority
Context Budgeting
Gemini has a massive context window (up to 1M tokens), but filling it costs money and latency.
Best Practice:
- Keep context under 100k tokens for fast responses
- Use
.geminiignoreto exclude generated files, logs, and large data - Only use the 1M window for "deep dive" architectural analysis
11. Integration with Other Tools
Git Integration
Gemini CLI respects .gitignore by default, but you can override this:
# ~/.gemini/config.yaml
files:
auto_gitignore: true # Default: true
It can also help with git operations:
# Generate commit message for staged changes
gemini git commit
# Explain changes in last commit
gemini git explain HEAD
Editor Integration
While CLI-native, Gemini can open files in your editor:
# Configure editor
export VISUAL="code --wait" # VS Code
# or
export VISUAL="vim" # Vim
# During chat, if Gemini suggests changes:
> /edit src/main.py
CI/CD Integration
Gemini CLI can be used in CI pipelines for code review:
# .github/workflows/gemini-review.yml
name: Gemini Code Review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
- name: Install Gemini CLI
run: pip install google-generativeai-cli
- name: Run Review
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
git fetch origin main
git diff origin/main > changes.diff
gemini review changes.diff
12. Production Examples
Example 1: Python Data Science Project
.gemini/instructions.md:
# Data Science Project Rules
## Tech Stack
- Python 3.10
- Pandas, NumPy, Scikit-learn
- Jupyter Notebooks
## Coding Standards
- Use vectorization over loops (Pandas/NumPy)
- Document data shapes in docstrings
- e.g., `def process(df: pd.DataFrame) -> np.ndarray:`
- Use `pathlib` for all file paths
## Notebooks
- Keep notebooks clean: restart kernel & run all before committing
- Move reusable logic to `src/` modules
- No hardcoded paths (use config)
## Visualization
- Use Seaborn with 'whitegrid' style
- Always label axes and add titles
- Save plots to `reports/figures/`
Example 2: Go Microservice
.gemini/instructions.md:
# Go Service Rules
## Tech Stack
- Go 1.22
- Gin framework
- gRPC for internal comms
## Go Idioms
- Handle errors explicitly: `if err != nil { return err }`
- Use `context.Context` for all I/O operations
- Prefer table-driven tests
- Group imports: stdlib, 3rd party, internal
## Project Structure
- `cmd/`: Main applications
- `internal/`: Private library code
- `pkg/`: Public library code
- `api/`: OpenAPI/gRPC specs
## Logging
- Use structured logging (slog)
- Include request ID in all logs
- Level: Info in prod, Debug in dev
Example 3: React/TypeScript Frontend
.gemini/instructions.md:
# React Frontend Rules
## Tech Stack
- React 18
- TypeScript 5
- Vite
- Tailwind CSS
## Components
- Functional components only
- Named exports: `export const Button = ...`
- Colocate styles/tests: `Button.tsx`, `Button.test.tsx`
## State Management
- Use React Query for server state
- Use Zustand for global client state
- Use `useState` for local UI state
## Accessibility
- All interactive elements must be keyboard accessible
- Semantic HTML (button, a, nav, main)
- Valid ARIA attributes where needed
13. Troubleshooting & Debugging
Common Issues
Issue 1: Instructions Not Being Followed
- Cause: Instructions file location incorrect or ignored.
-
Fix: Run
gemini config listto verify paths. Ensure file is named exactly.gemini/instructions.md. - Debug: Add a unique string to instructions (e.g., "SECRET_WORD") and ask Gemini "What is the secret word?" to verify loading.
Issue 2: Context Too Large
- Cause: Including large datasets, logs, or dependencies.
-
Fix: Update
.geminiignoreto excludenode_modules,.venv,data/, etc. -
Check: Run
gemini context showto see included files.
Issue 3: Authentication Failures
- Cause: Expired token or wrong project.
-
Fix: Run
gcloud auth application-default loginagain or refresh API key. -
Verify:
gemini auth check.
Issue 4: Slow Responses
- Cause: Context window too full or complex instructions.
-
Fix: Switch to
gemini-2.5-flashmodel for speed, or reduce context size in.gemini/context.yaml.
Debugging Commands
# Show current configuration
gemini config list
# Show loaded instructions
gemini instructions show
# Show active context
gemini context show
# Test API connection
gemini doctor
# View last request/response (including system prompt)
gemini history last --verbose
14. Best Practices
1. Treat Instructions as Code
- Version control your
.gemini/instructions.md. - Review changes in PRs.
- Keep them updated as project evolves.
2. Be Specific, Not Verbose
- Bad: "Please try to write good code that is clean."
- Good: "Use snake_case for variables. Max line length 80 chars."
3. Use Examples
- LLMs learn best from few-shot examples.
- Include a "Golden Path" example for common tasks (e.g., "How to add a new endpoint").
4. Layer Your Rules
- Global: Personal preferences (e.g., "I like concise answers").
- Project: Team standards (e.g., "Use Pytest").
- Directory (if supported): Module specifics.
5. Regular Maintenance
- Schedule a quarterly review of
instructions.md. - Remove rules that are no longer relevant.
- Add rules for common mistakes Gemini makes.
6. Security First
- Explicitly forbid hardcoded secrets in instructions.
- Use
.geminiignoreto protect sensitive files. - Enable safety filters in config.
15. Comparison with Other CLI Tools
| Feature | Gemini CLI | Aider | GitHub Copilot CLI | Claude Code |
|---|---|---|---|---|
| Model | Gemini 3 Pro (1M context) | Multi-model (Claude, GPT-4) | GPT-4o | Claude 3.5 Sonnet |
| Config Format | Markdown (instructions.md) |
YAML (.aider.conf.yml) |
None/Limited | Markdown (CLAUDE.md) |
| Context | Automatic Semantic Index | Repo Map | Limited | Project Index |
| Multi-modal | Native (Images/Video) | Images only | No | No |
| Price | Free tier available | BYO Key | Subscription | BYO Key |
| Ecosystem | Google Cloud | Independent | GitHub | Anthropic |
When to Choose Gemini CLI?
- You are deep in the Google Cloud ecosystem.
- You need the massive 1M token context window (e.g., analyzing entire codebases).
- You want multi-modal capabilities (e.g., "Explain this architecture diagram").
- You prefer a free/low-cost entry point (generous free tier).
Conclusion
Configuring Gemini CLI effectively turns it from a generic chatbot into a specialized senior engineer on your team. By leveraging .gemini/instructions.md and the configuration hierarchy, you ensure every interaction adheres to your project's specific standards, architecture, and best practices.
Start simple with the provided templates, and iterate as you discover what rules yield the best results for your specific workflow.
Top comments (0)