Problem: Windsurf Forgets Your Project Standards Every Session
Windsurf Rules project context lets you encode your architecture decisions, coding conventions, and repo structure once — so the AI agent stops asking the same questions and stops suggesting patterns that don't fit your stack.
Without rules, Cascade rewrites your Zod schemas in a style you don't use, picks the wrong test runner, and ignores your monorepo boundaries. You end up re-explaining your stack on every task.
You'll learn:
- How to write a
.windsurfrulesfile that gives Cascade accurate project memory - How global rules and workspace rules interact — and which wins
- Three rule templates for TypeScript monorepos, Python FastAPI projects, and full-stack Next.js apps
Time: 15 min | Difficulty: Intermediate
Why This Happens
Cascade is a stateless agent. It has no persistent memory of your project between sessions unless you give it an explicit context file. Each new task starts from scratch.
.windsurfrules is the mechanism Windsurf provides to inject always-on context into every Cascade prompt. Think of it as a system prompt that's scoped to your workspace.
Symptoms you're missing rules:
- Cascade suggests
axioswhen your project usesfetch+ky - It generates
classcomponents in a hooks-only React codebase - It creates new files outside your
src/structure - It adds
console.logdebug statements in production code paths - It picks
unittestin a project that's standardized onpytest
How Windsurf Rules Work
Windsurf loads rules from two locations at startup:
| Layer | File | Scope |
|---|---|---|
| Global | ~/.codeium/windsurf/global_rules.md | All workspaces |
| Workspace | .windsurfrules at repo root | Current project only |
Both files are injected into Cascade's context on every turn. Workspace rules are appended after global rules — meaning workspace rules take precedence when they conflict.
There is a 6,000 token limit across both files combined. Budget global rules for universal preferences (language, formatting) and workspace rules for project-specific architecture.
Solution
Step 1: Create the Workspace Rules File
At your repo root, create .windsurfrules:
touch .windsurfrules
Then open it in Windsurf. Cascade can read and edit this file like any other — you can ask it to update its own rules as the project evolves.
Commit this file to version control. Every developer on the team shares the same agent behavior.
Step 2: Write Your Project Context Block
Start with a project summary section. This is the single highest-value block — it orients Cascade before any task.
# Project Context
## Stack
- Runtime: Node 22 + TypeScript 5.4 (strict mode)
- Framework: Next.js 15 App Router — no Pages Router
- Styling: Tailwind CSS 4 + shadcn/ui — no CSS modules, no styled-components
- State: Zustand for client state, React Query v5 for server state
- Auth: Clerk — never roll custom auth
- DB: PostgreSQL 16 via Drizzle ORM — no raw SQL unless in a migration file
- Testing: Vitest + Playwright — no Jest, no Cypress
- Package manager: pnpm — never suggest npm or yarn commands
## Monorepo Structure
apps/
web/ # Next.js frontend
api/ # Hono API on Bun
packages/
db/ # Drizzle schema + migrations
ui/ # shadcn component library
config/ # Shared ESLint, TypeScript, Tailwind configs
Why this matters: Cascade checks this block before suggesting imports. A package manager line alone saves 10+ correction cycles per week on a shared codebase.
Step 3: Add Coding Standards
Separate standards from context. Keep them scannable.
## Coding Standards
### TypeScript
- Always use `type` for object shapes, `interface` only for extendable contracts
- No `any` — use `unknown` + type guard or `z.infer<typeof schema>`
- Prefer `const` arrow functions over `function` declarations in components
- Export types from `types.ts` in each package — never inline in function signatures
### File Naming
- Components: PascalCase (`UserCard.tsx`)
- Utilities: camelCase (`formatDate.ts`)
- Route handlers: lowercase (`route.ts` — Next.js convention)
- Test files: co-located, `.test.ts` suffix (`UserCard.test.tsx`)
### Error Handling
- Server actions throw `ActionError` from `@/lib/errors` — never throw raw `Error`
- API routes return `{ error: string; code: string }` on failure — never expose stack traces
- All async functions must have explicit return types
### Imports
- Use `@/` path alias for `apps/web/src/` — no relative `../../` beyond one level
- Barrel exports via `index.ts` in each package — import from package, not deep path
Step 4: Add Agent Behavior Rules
This section tells Cascade how to work, not just what the project looks like.
## Agent Behavior
### Before writing code
- State what you're about to change and why — one sentence
- If the task touches the DB schema, confirm the migration strategy before writing
### File creation
- Never create files outside the existing directory structure without asking
- If a utility already exists in `packages/`, use it — do not duplicate
### Code style
- No `console.log` in production paths — use the logger at `@/lib/logger`
- No TODO comments unless the task explicitly asks to leave one
- Match the surrounding code style — if existing code uses early returns, use early returns
### Testing
- Every new exported function gets a Vitest unit test
- Every new route gets a Playwright API test in `tests/api/`
- Do not modify existing tests to make new code pass — fix the code
### Commits
- Use conventional commits: `feat:`, `fix:`, `chore:`, `refactor:`
- Scope to the package: `feat(web):`, `fix(api):`
Step 5: Configure Global Rules for Universal Preferences
Edit ~/.codeium/windsurf/global_rules.md for preferences that apply across all your projects:
mkdir -p ~/.codeium/windsurf
nano ~/.codeium/windsurf/global_rules.md
# Global Rules
## Language
- Respond in English
- Be direct — no preamble, no "Great question!", no "Certainly!"
- When uncertain, say so and give the two most likely options
## Code output
- Show diffs when editing existing files, not full file rewrites
- Add a one-line comment explaining WHY for any non-obvious logic
- Prefer explicit over clever — no one-liners that sacrifice readability
## Security
- Never hardcode secrets, API keys, or credentials
- Always flag if a suggested change touches auth, permissions, or data access
Expected result: Open any workspace — Cascade now applies these preferences automatically.
Step 6: Verify Rules Are Loading
Open Windsurf, start a new Cascade task, and ask:
What rules are you operating under for this project?
Cascade will summarize the rules it has loaded. If it can't describe your stack accurately, the file has a syntax issue or is in the wrong location.
Confirm the file is at the repo root:
ls -la .windsurfrules
# -rw-r--r-- 1 user staff 2847 Mar 11 09:00 .windsurfrules
Rule Templates
Template: Python FastAPI Project
# Project Context
## Stack
- Python 3.12 + uv for package management — no pip, no poetry
- Framework: FastAPI 0.115 — no Flask, no Django
- Validation: Pydantic v2 — always use `model_validator` not `validator`
- DB: PostgreSQL via SQLAlchemy 2.0 async — no sync sessions in route handlers
- Testing: pytest + httpx AsyncClient — no unittest
- Linting: Ruff — no flake8, no black (Ruff handles formatting too)
## Project Structure
src/
api/ # FastAPI routers
models/ # SQLAlchemy ORM models
schemas/ # Pydantic request/response schemas
services/ # Business logic — no DB calls in routers
core/ # Config, DB session, dependencies
## Standards
- Services own DB logic — routers call services, never query directly
- All endpoints are async — no sync def route handlers
- Dependency injection via `Depends()` for auth and DB sessions
- Return types always explicit on route handlers
Template: TypeScript Monorepo (Turborepo)
# Project Context
## Stack
- Node 22 + TypeScript 5.4 strict
- Build: Turborepo with pnpm workspaces
- API: Hono on Bun runtime — no Express
- Frontend: SvelteKit 2 — no React in this project
- DB: Drizzle ORM + PlanetScale (MySQL 8 compatible)
- Testing: Vitest across all packages
## Turborepo Pipeline
- `turbo build` — transpile all packages before apps
- `turbo test` — run affected tests only (--filter flag)
- Never run `tsc` directly — always `turbo typecheck`
## Shared Packages
packages/db — Drizzle schema, migrations, seed scripts
packages/utils — Pure functions, no framework deps
packages/env — t3-env schema for type-safe environment variables
Verification
After setting up your rules, run this Cascade prompt on a real task:
Create a new user profile endpoint. Follow project conventions.
You should see:
- Correct file placed in your router directory (not invented location)
- Correct ORM / DB pattern from your rules
- No package manager mismatch
- Test file created alongside the feature file
If Cascade deviates, add a more specific rule to the failing section. Rules improve iteratively — treat them like code.
What You Learned
.windsurfrulesat repo root is workspace-scoped;global_rules.mdis user-scoped — workspace wins on conflicts- The 6,000 token budget forces you to prioritize: architecture decisions over style nitpicks
- Committing
.windsurfrulesto version control standardizes AI behavior across the entire team - Agent behavior rules (how to work) are as important as project context rules (what the project is)
Tested on Windsurf 1.9, macOS Sequoia 15.3 & Ubuntu 24.04
FAQ
Q: Does .windsurfrules work with Windsurf's offline/local model mode?
A: Yes. Rules are injected at the prompt level before any model call — they work with both cloud and local model backends.
Q: What happens if my rules exceed the 6,000 token limit?
A: Windsurf silently truncates from the bottom. Put your highest-priority context at the top of each file, and check token count with a tokenizer like tiktoken if your file is large.
Q: Can I have per-directory rules in a monorepo?
A: Not natively. One .windsurfrules per repo root. Work around this by structuring your rules with explicit section headers per app/package and telling Cascade which section applies.
Q: How is this different from Cursor's .cursorrules?
A: The file format is similar — both inject into system context. Windsurf's Cascade has deeper flow awareness (it can plan multi-file edits before starting), so rules about file creation and dependency boundaries have more effect than in Cursor's single-turn Composer.
Q: Should I add .windsurfrules to .gitignore?
A: No — commit it. The whole value is that every developer and every CI session runs Cascade with the same project context. Only add it to .gitignore if the file contains secrets, which it should never do.