DEV Community

Yigit Konur
Yigit Konur

Posted on

Complete Guide: How to Set AI Coding Rules for Gemini CLI

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Configuration Loading Priority

Gemini CLI loads configuration in this order (later overrides earlier):

  1. Built-in defaults (Gemini's base behavior)
  2. Global config (~/.gemini/config.yaml)
  3. Global instructions (~/.gemini/instructions.md)
  4. Project config (.gemini/context.yaml)
  5. Project instructions (.gemini/instructions.md) ⭐ Highest Priority
  6. 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
Enter fullscreen mode Exit fullscreen mode

Global User-Level:

~/.gemini/instructions.md
Enter fullscreen mode Exit fullscreen mode

How It Works

Loading Mechanism:

  1. User runs gemini chat in project directory
  2. Gemini CLI searches for .gemini/instructions.md in current directory
  3. If found, entire file content prepended to system prompt
  4. If not found, searches for ~/.gemini/instructions.md
  5. 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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.)
Enter fullscreen mode Exit fullscreen mode

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!)
Enter fullscreen mode Exit fullscreen mode

.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)
Enter fullscreen mode Exit fullscreen mode

.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/
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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:

Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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()
Enter fullscreen mode Exit fullscreen mode

❌ 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**:

Enter fullscreen mode Exit fullscreen mode


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)
Enter fullscreen mode Exit fullscreen mode

**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

Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

### Database Patterns

#### Repository Pattern
Enter fullscreen mode Exit fullscreen mode


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()
Enter fullscreen mode Exit fullscreen mode

---

## TESTING REQUIREMENTS

### Test Structure

Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode

@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()
Enter fullscreen mode Exit fullscreen mode

### Coverage Requirements
- **Unit Tests**: 90% minimum (business logic, services, repositories)
- **Integration Tests**: 80% minimum (API endpoints)
- **E2E Tests**: Critical user flows only

### Test Commands
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode

### Input Validation (ALWAYS)
Enter fullscreen mode Exit fullscreen mode


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()
Enter fullscreen mode Exit fullscreen mode

❌ WRONG - No validation

@router.post("/products")
async def create_product(data: dict): # dict has no validation!
product = Product(**data) # Dangerous!


### Never Hardcode Secrets
Enter fullscreen mode Exit fullscreen mode


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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


bash

Run tests

pytest

With coverage

pytest --cov=app --cov-report=term-missing

Run linters

ruff check app/
mypy app/


### Deployment
Enter fullscreen mode Exit fullscreen mode


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**:
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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.*
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Built-in Gemini defaults
  2. Global instructions (~/.gemini/instructions.md)
  3. Project instructions (.gemini/instructions.md) ← Highest priority

Example Combination:

Global (~/.gemini/instructions.md):

# Global Rules

I prefer:
- Concise responses
- Functional programming
- TypeScript over JavaScript
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Context-Aware Examples

## API Endpoint Creation Pattern

When asked to create a new API endpoint, follow this exact sequence:

### Step 1: Define Pydantic Schemas
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode

### Step 2: Create Repository Method
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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.
Enter fullscreen mode Exit fullscreen mode

Anti-Pattern Prevention

## Code Anti-Patterns to AVOID

### ❌ FORBIDDEN Pattern 1: Hardcoded Configuration
Enter fullscreen mode Exit fullscreen mode


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**:
Enter fullscreen mode Exit fullscreen mode


python
from pydantic import BaseSettings

class Settings(BaseSettings):
database_url: str
api_key: str
max_retries: int = 5

class Config:
    env_file = ".env"
Enter fullscreen mode Exit fullscreen mode

settings = Settings()


---

### ❌ FORBIDDEN Pattern 2: Swallowing Exceptions
Enter fullscreen mode Exit fullscreen mode


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**:
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


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**:
Enter fullscreen mode Exit fullscreen mode


python
@router.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
service = UserService(db)
return await service.list_users() # Service layer

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Method 3: Environment Variable

export GEMINI_MODEL="gemini-2.5-flash-lite"
gemini chat
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Scans all non-ignored files
  2. Chunks code into logical blocks
  3. Generates embeddings
  4. Stores index in .gemini/index/

To force reindexing:

gemini index --rebuild
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

Inside a chat session:

> /add src/services/user_service.py
> /remove src/main.py
> /clear (resets context)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .geminiignore to 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/`
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

13. Troubleshooting & Debugging

Common Issues

Issue 1: Instructions Not Being Followed

  • Cause: Instructions file location incorrect or ignored.
  • Fix: Run gemini config list to 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 .geminiignore to exclude node_modules, .venv, data/, etc.
  • Check: Run gemini context show to see included files.

Issue 3: Authentication Failures

  • Cause: Expired token or wrong project.
  • Fix: Run gcloud auth application-default login again 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-flash model 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
Enter fullscreen mode Exit fullscreen mode

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 .geminiignore to 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)