Windsurf is Codeium’s AI coding editor. It ships with Cascade, a multi-step agentic AI that can read files, run terminal commands, and make changes across your codebase. To control how Cascade behaves in your project, you use .windsurfrules.
If you have used .cursorrules with Cursor, the concept is identical. If you have used CLAUDE.md with Claude Code, it is similar but with some important structural differences. This guide covers all of it — what .windsurfrules is, how it works, how to write effective rules, and a collection of ready-to-use examples for common project types.
What Is .windsurfrules?
.windsurfrules is a plain text file you place in the root of your project. When Cascade works on files in that directory (or any subdirectory), it reads the rules file and applies those instructions to everything it does in your project.
The rules are freeform text. You are not filling out a schema or picking from a menu — you are writing instructions in natural language, the same way you would explain your project to a new team member on their first day. What stack are you using? What conventions do you follow? What should the AI never do without asking?
Cascade integrates .windsurfrules into its system context, so the instructions persist across the entire session. You do not need to repeat yourself in every prompt.
Global vs Project Rules
Windsurf gives you two levels of rules:
Global rules — applied to every project, every session. Set them in Windsurf’s settings panel under “Cascade” → “Custom Instructions”. Use these for personal preferences that are always true: your preferred language, how you like code to be commented, your debugging style.
Project rules (.windsurfrules) — applied only to the current project. This is where team conventions, framework-specific behavior, and project-specific constraints belong. Commit this file to your repository so every team member gets the same behavior.
When both exist, Windsurf merges them. Project rules take precedence when they conflict with global rules.
How .windsurfrules Differs from .cursorrules and CLAUDE.md
All three formats serve the same fundamental purpose — give the AI persistent context about your project — but they have different designs.
| Feature | .windsurfrules | .cursorrules | CLAUDE.md |
|---|---|---|---|
| Format | Freeform text | Freeform text | Markdown |
| Scope | Per-project | Per-project | Per-project or per-directory |
| Multiple files | No (one per project root) | No | Yes (nested, hierarchical) |
| Subagent aware | Yes (Cascade agents) | No | Yes (Claude subagents) |
| Shell commands | No | No | Yes (via hooks) |
| Global variant | Yes (Windsurf settings) | Yes (.cursorrules global) | No |
The most practical difference between .windsurfrules and CLAUDE.md is the hierarchy. Claude Code lets you place CLAUDE.md files in subdirectories, and each one applies only within its directory. Windsurf currently uses a single .windsurfrules file at the project root. If you have a monorepo with distinct frontend and backend conventions, you need to handle that in one file.
The practical difference between .windsurfrules and .cursorrules is mostly the AI model. .cursorrules talks to Cursor’s model. .windsurfrules talks to Cascade. The rule-writing principles are the same, but Cascade is particularly strong at multi-step agentic tasks, so rules about task completion and verification matter more here.
Writing Effective .windsurfrules
The common failure mode with all AI rules files is writing aspirational instructions that sound good but do not change behavior. “Write clean code” does nothing. “Follow the principle of least surprise” is meaningless without context. The rules that actually work are specific, testable, and grounded in your real project.
Here are the patterns that work:
1. State facts before giving instructions
Start with what is true about your project, not what you want the AI to do. The AI uses factual context to make better decisions.
This is a Next.js 15 app using App Router, TypeScript strict mode,
Tailwind CSS, and Drizzle ORM with a PostgreSQL database.
We use pnpm for package management.
This context implicitly prevents a dozen wrong behaviors — suggesting npm instead of pnpm, writing JavaScript instead of TypeScript, using the Pages Router, recommending Prisma when you are on Drizzle.
2. Be explicit about what to check before making changes
Cascade can make sweeping changes quickly. Tell it when to slow down and verify first.
Before refactoring any function, check if it is exported and used
in more than one file. If it is, list the affected call sites
before making changes.
3. Specify the testing contract
If your project has tests, tell the AI what tests exist and what the expected state is after any change.
Run `pnpm test` after any code change. All tests must pass before
considering a task complete. If a test fails that was passing before
your change, fix it before moving on.
4. Name the patterns you actually use
Generic rules like “use functional patterns” are interpreted differently by every AI. Name the actual patterns your codebase uses.
Use the repository pattern for data access. All database queries go
in /src/repositories/. Services call repositories; they never query
the database directly.
5. Set guardrails for destructive actions
Cascade can run terminal commands. Decide which ones require your confirmation.
Never run `DROP TABLE` or `DELETE FROM` without first showing me
the query and getting my explicit approval.
Never run database migrations automatically. Show me the migration
file and explain what it does before running `drizzle-kit push`.
12 Ready-to-Use .windsurfrules Examples
1. Next.js 15 (App Router)
Project: Next.js 15 app with App Router, TypeScript strict mode,
Tailwind CSS, shadcn/ui, and Drizzle ORM + PostgreSQL.
Package manager: pnpm.
Conventions:
- All new pages go in /src/app/ following App Router conventions.
- Server Components by default. Add "use client" only when strictly required
(event handlers, browser APIs, hooks). Explain why in a comment.
- Data fetching happens in Server Components or server actions.
Never fetch from the client unless there is no alternative.
- Use the cn() utility from @/lib/utils for conditional Tailwind classes.
- shadcn/ui components live in /src/components/ui/. Do not modify them.
Put custom components in /src/components/.
- Database queries go in /src/db/queries/. Services call queries;
never import db directly into a component.
- Environment variables: server-only variables go in .env.local.
Variables exposed to the client must be prefixed with NEXT_PUBLIC_.
Testing:
Run `pnpm test` after any change. All tests must pass before finishing.
If you add a new route or server action, add a test for it.
Never:
- Use the Pages Router.
- Use `any` in TypeScript without a comment explaining why.
- Install a new package without confirming with me first.
2. Python FastAPI Service
Project: FastAPI service with Python 3.12, Pydantic v2, SQLAlchemy 2.0,
and PostgreSQL. Dependency injection via FastAPI's Depends().
Conventions:
- All route handlers go in /src/routers/.
- Business logic goes in /src/services/. Routers call services;
services do not import from routers.
- Database models go in /src/models/. Pydantic schemas go in /src/schemas/.
Never mix SQLAlchemy models and Pydantic schemas.
- Use async/await throughout. No synchronous database calls.
- Raise HTTPException with appropriate status codes. Never let exceptions
bubble up to the client uncaught.
- Use Pydantic v2 syntax (model_config, not class Config).
Testing:
Run `pytest -x` after any change. The -x flag stops on first failure.
All tests must pass before marking a task complete.
Database:
Never write raw SQL. Use SQLAlchemy ORM or Core expressions.
Always use Alembic for schema changes. Never modify the database
schema directly. Show me migration files before running them.
3. React Native (Expo)
Project: Expo 52 app with TypeScript, NativeWind (Tailwind for RN),
Expo Router, and React Query for data fetching.
Targets iOS and Android.
Conventions:
- Use Expo Router file-based routing. Screens go in /app/.
- Use NativeWind for styling. Do not use StyleSheet.create() unless
NativeWind cannot handle the use case.
- Use React Query for all server state. No useState + useEffect data fetching.
- Shared components go in /components/. Screen-specific components
go in the same directory as the screen.
- Platform-specific code uses the .ios.tsx / .android.tsx extension pattern.
Never:
- Use libraries that are not Expo-compatible without confirming with me.
- Eject from Expo without discussing it first.
- Use console.log in production code (use a logger utility instead).
4. Django REST API
Project: Django 5.1 with Django REST Framework, PostgreSQL via psycopg3,
and Celery for async tasks.
Conventions:
- Apps are feature-based (e.g., /accounts/, /orders/, /notifications/).
Each app has its own models.py, serializers.py, views.py, urls.py.
- Use class-based views (ViewSet preferred). Avoid function-based views
unless they are genuinely simpler.
- Serializers do all validation. Never validate in views.
- Use select_related() and prefetch_related() whenever querying related models.
Explain the N+1 problem you are avoiding when you add these.
- Celery tasks go in /tasks.py within each app. Tasks must be idempotent.
Testing:
Run `python manage.py test` after any change.
Use Django's TestCase for DB tests. Use APITestCase for endpoint tests.
Database:
Never hand-edit migration files unless fixing a merge conflict.
Run `makemigrations --check` to verify models match migrations.
5. CLI Tool (Node.js)
Project: Node.js CLI tool using TypeScript, Commander.js, and
the Ink library for terminal UI. Distributed via npm.
Node.js version: 20 LTS.
Conventions:
- Entry point: /src/cli.ts. Keep it minimal — parse args, call commands.
- Commands go in /src/commands/. Each command is a separate file.
- Shared utilities go in /src/utils/.
- Use Ink components for any interactive or formatted terminal output.
Avoid console.log in command handlers.
- Errors should be caught and displayed via Ink's error components,
not thrown to the top level.
Package:
- The package.json `bin` field points to the compiled output in /dist/.
- Run `npm run build` before `npm link` for local testing.
- Never publish with `npm publish` directly. Use `npm publish --dry-run`
first and show me the output.
6. Go Microservice
Project: Go 1.23 microservice using standard library HTTP (net/http),
pgx for PostgreSQL, and zerolog for structured logging.
Conventions:
- Folder structure: /cmd/ for main packages, /internal/ for all
application code. Nothing in /internal/ should be imported externally.
- Use the standard library for HTTP routing (Go 1.22+ pattern matching).
No router frameworks unless the project already has one.
- All errors must be wrapped with context using fmt.Errorf("...: %w", err).
Never discard errors with _.
- Logging: use zerolog. All log entries must include a "component" field.
Use log.Error().Err(err).Str("component", "...").Msg("...") pattern.
- Database queries go in /internal/repository/. No direct pgx calls
in handlers or services.
Testing:
Run `go test ./...` after any change. All tests must pass.
Use table-driven tests for functions with multiple input cases.
7. Data Science / Jupyter
Project: Data science project using Python 3.11, pandas 2.x,
scikit-learn, and Jupyter notebooks. Reproducibility is critical.
Conventions:
- Notebooks are for exploration and presentation only. Reusable code
belongs in /src/ as Python modules.
- Never hardcode file paths in notebooks. Use pathlib and relative paths
from the project root.
- Pin all dependencies in requirements.txt with exact versions.
When adding a library, add it with the current version to requirements.txt.
- Use pandas chaining style where possible. Avoid modifying DataFrames in-place.
- Random seeds: always set np.random.seed() and pass random_state= to
sklearn functions for reproducibility.
Data:
Never commit data files larger than 1MB to git.
Large datasets go in /data/raw/ which is in .gitignore.
Document the data source and download steps in /data/README.md.
8. Chrome Extension (Manifest V3)
Project: Chrome Extension using Manifest V3, TypeScript, and
Vite as the build tool. No framework (vanilla TypeScript).
Conventions:
- Background script: /src/background/. Runs as a service worker.
Cannot use DOM APIs.
- Content scripts: /src/content/. Can access the page DOM.
Cannot access chrome.storage directly — use message passing.
- Popup: /src/popup/. Standard DOM with access to Chrome APIs.
- Use chrome.runtime.sendMessage() for all communication between contexts.
Document the message shape with a TypeScript type.
- Store state in chrome.storage.local. Never store sensitive data
(tokens, passwords) in storage without encryption.
Build:
Run `pnpm build` to compile. Load /dist/ as an unpacked extension for testing.
After any change to manifest.json, tell me — the extension must be
reloaded in chrome://extensions.
Permissions:
Never add a new permission to manifest.json without confirming with me.
Explain why the permission is needed and what user data it accesses.
9. Monorepo (Turborepo)
Project: Turborepo monorepo with three packages:
- /apps/web — Next.js 15 frontend
- /apps/api — Fastify API
- /packages/ui — shared React components
- /packages/types — shared TypeScript types
Conventions:
- Shared types go in /packages/types/. Both apps import from there.
- Shared UI components go in /packages/ui/. The web app imports from there.
- Never import from /apps/web in /apps/api or vice versa.
- Run `turbo run build` (not individual package builds) to ensure
the dependency graph is respected.
- All packages use the same TypeScript config base: /tsconfig.base.json.
When making changes:
- Identify which package(s) are affected before making changes.
- If a change in /packages/types affects both apps, call that out.
- Run `turbo run test` after any change to catch cross-package breaks.
10. Infrastructure as Code (Terraform)
Project: Terraform configuration for AWS infrastructure.
Terraform version: 1.9+. Backend: S3 + DynamoDB state locking.
Conventions:
- Environments: /environments/dev/ and /environments/prod/ contain
the root modules. /modules/ contains reusable modules.
- All resources must have a Name tag and an Environment tag.
Use the local.common_tags pattern already established in the codebase.
- Variables go in variables.tf. Outputs go in outputs.tf.
Main resource definitions go in main.tf. Keep these separate.
- Use data sources to reference existing resources, not hardcoded IDs.
Safety rules:
Never run `terraform apply` directly. Run `terraform plan` first and
show me the plan output, especially any "destroy" actions.
If the plan includes any resource destruction, stop and explain the
impact before proceeding.
Never store secrets in terraform.tfvars. Use AWS Secrets Manager or
environment variables.
11. Open Source Library
Project: Open source TypeScript utility library. Published on npm.
Supports Node.js 18+, browser, and Deno environments.
Zero runtime dependencies.
Conventions:
- Public API is defined in /src/index.ts. Do not add exports without
discussing API design first.
- All public functions must have JSDoc comments with @param, @returns,
and @example sections.
- No dependencies. If you want to add one, I need to approve it first.
- Build output: ESM (/dist/esm/), CommonJS (/dist/cjs/), and type
declarations (/dist/types/).
Breaking changes:
Any change that modifies the signature of an exported function or
removes an export is a breaking change. Flag it before making it.
I will decide if it warrants a major version bump.
Testing:
Run `pnpm test` after every change. Test coverage must not decrease.
If you add a new exported function, add tests for it before finishing.
12. Documentation Site (Astro)
Project: Documentation site built with Astro 5, Starlight theme,
and MDX for content. Hosted on Cloudflare Pages.
Conventions:
- Documentation content goes in /src/content/docs/.
- All pages must have title and description in frontmatter.
- Use Starlight's built-in components (Aside, Tabs, Steps, etc.)
rather than creating custom components for common patterns.
- Code examples must be complete and runnable. No pseudo-code.
Every code block must specify a language for syntax highlighting.
- Internal links use relative paths. External links include the
full URL and open in a new tab with rel="noopener".
When adding a new page:
- Add it to the sidebar in /astro.config.mjs.
- Check that it is linked from at least one other page.
- Run `pnpm build` to verify there are no broken links.
Common Mistakes to Avoid
Rules that contradict the actual codebase. If your rules say “use functional components only” but your codebase has class components, Cascade gets conflicting signals. Rules should describe what is true right now, not what you aspire to. The aspirational rules go in a separate CONTRIBUTING.md or ADR.
Overloading with rules nobody would actually break. “Write readable code” and “use descriptive variable names” are noise. Cascade already does these things. Rules earn their spot by changing behavior you would otherwise get wrong.
Rules without context about why. “Never use useEffect for data fetching” is good. “Never use useEffect for data fetching — use React Query instead because our caching layer depends on it” is better. The why helps Cascade make good decisions in edge cases you did not anticipate.
Forgetting to commit the file. .windsurfrules should be version-controlled. If it’s in .gitignore, your teammates are not getting the benefit.
Combining .windsurfrules with Other AI Rules Files
If your team uses multiple AI tools, you may have both a .windsurfrules for Windsurf users and a CLAUDE.md for Claude Code users (or .cursorrules for Cursor users). This is normal and works fine — each AI tool reads its own rules file.
The risk is drift. When you update one file, you may forget to update the others. One approach that works: keep a single canonical AI_RULES.md in your repo as the source of truth, then sync its content into the tool-specific files as part of your release process. A simple shell script can do this.
#!/bin/bash
# sync-ai-rules.sh
# Copy canonical AI rules into tool-specific files
SOURCE="AI_RULES.md"
cp "$SOURCE" ".windsurfrules"
cp "$SOURCE" "CLAUDE.md"
cp "$SOURCE" ".cursorrules"
echo "AI rules synced to all tool-specific files."
This is an intentional simplification — each tool has unique capabilities that benefit from tool-specific instructions — but for the 80% of rules that apply to any AI tool, syncing keeps things consistent.
Verifying Your Rules Work
After writing a .windsurfrules, test it. Ask Cascade to do something your rules are supposed to control and see if it follows them. Some things to verify:
- File placement: Ask Cascade to add a new component. Does it put it in the right directory?
- Package manager: Ask Cascade to install a library. Does it use the right package manager?
- Testing: Make a small change and ask Cascade to verify it is complete. Does it run the tests?
- Guardrails: Ask Cascade to do something your rules prohibit (like running a migration). Does it stop and ask?
If something is not working, make the rule more explicit. Vague instructions are interpreted generously by the AI. Specific instructions are followed precisely.
The quality of your AI coding experience is directly proportional to the quality of your rules. A well-written .windsurfrules file means Cascade acts like a senior developer who knows your project — not a generic assistant who guesses. The templates above are starting points. Edit them to match your actual codebase and you will get far better results out of the box.