Skip to content

AI Instruction Files

Every assistant wants its rules in a different file. Claude Code reads CLAUDE.md, Cursor reads .cursorrules, Windsurf reads .windsurfrules, Copilot reads .github/copilot-instructions.md, and the cross-vendor convention is AGENTS.md. Maintaining the same guidance in five files is how four of them end up stale.

rebase init writes all five — as pointers to a single file you actually edit:

your-project/
├── ai-instructions.md ← the real content
├── CLAUDE.md ← pointer
├── AGENTS.md ← pointer
├── .cursorrules ← pointer
├── .windsurfrules ← pointer
└── .github/
└── copilot-instructions.md ← pointer

Each pointer file is three lines:

# Rebase AI Rules
Please refer to and follow the instructions defined in [ai-instructions.md](./ai-instructions.md).
The Rebase skills for this assistant live in `.claude/skills/`. If they are not there yet, install them, with the Rebase MCP server: `rebase skills install --agent claude --mcp`.

On 0.23 the third line only tells the assistant to run rebase skills install --agent claude, and the .mcp.json below starts the server with npx -y @rebasepro/mcp from the registry.

The others differ only in the agent’s skills directory and its --agent value — --agent cursor, --agent windsurf, --agent codex, --agent copilot — and in the relative path, which is ../ai-instructions.md in .github/copilot-instructions.md and ai-instructions.md in AGENTS.md. The third line is conditional because rebase init --agent may already have installed the skills before the assistant ever reads it.

This happens on every rebase init, for every preset including --headless. There is no flag and no prompt.

rebase init also writes .mcp.json, which points Claude Code, Cursor and any other MCP client at the Rebase MCP server — the project’s own copy, a devDependency pinned with the CLI (npx --no rebase-mcp in an npm project):

{
"mcpServers": {
"rebase": {
"command": "pnpm",
"args": ["exec", "rebase-mcp"],
"env": {
"REBASE_PROJECT_DIR": "."
}
}
}
}

REBASE_PROJECT_DIR is ".", not an absolute path: the client spawns the server at the project root, and an absolute path is the one line of that file that cannot be committed. It is there because ~/.rebase/projects.json is machine-wide — a project that names no directory of its own falls through to whatever the last project on that machine persisted. See the precedence.

The pointer files are deliberately content-free. Assistants follow relative Markdown links, so a three-line file that names the real one gets the same result as a copy — and it has properties a copy does not:

  • One file to edit. Rules cannot drift between assistants, because there is only one set of rules.
  • One diff to review. A change to project conventions is a change to one file, not five identical ones a reviewer must compare.
  • Adding an assistant is three lines. A new tool with a new filename gets a pointer, not a sixth copy of your conventions.

The pattern is worth keeping if you fork the scaffold, and worth adopting in repos that are not Rebase projects at all.

The scaffolded file is deliberately short — it points at rebase skills install for depth, then states the rules that assistants get wrong often enough to be worth repeating at the top of every session:

  1. Schema as code. Collections are defined in config/collections/. Never hand-edit the generated Drizzle schema or the Postgres tables — see Schema as Code.
  2. Applying a collection change depends on the database. While pnpm dev is running, saving the collection file is the whole step: boot regenerates the Drizzle schema and creates the missing tables and columns. pnpm db:push is only for what boot deliberately leaves alone — a renamed column, a narrowed type, a removed field — on the managed development database as on your own PostgreSQL. Production is pnpm db:generate then pnpm db:migrate. Since 0.25 On 0.24, pnpm db:push needs your own PostgreSQL, and the file says so.
  3. Use the SDK. Go through rebase.dataAsAdmin.<slug> for work done as the service identity, or getDriver(c) inside a function when the read should run as the caller. The server client has no plain data accessor. Raw SQL and direct Drizzle calls bypass validation, callbacks and RLS.
  4. Guard every custom route. Routes in backend/functions/ are mounted without authentication. Use requireAuth / requireAdmin from @rebasepro/server/functions in the route’s own middleware slot — reading c.get("user") is not a guard, and neither is app.use() after the route.

That last one is the one to keep. It is the difference between a middleware that runs and one that does not, and an assistant that has not been told will reliably write the version that does not — see Custom Functions.

requireAdmin admits the admin role only. When a narrower role or an API key should be able to call a route, the guard to add is requireScope, with a scope the app declares — see Roles and scopes.

ai-instructions.md is your file. Nothing regenerates or overwrites it — unlike installed skills, which are replaced on every rebase skills install. Project-specific conventions belong here.

What earns its place is what an assistant cannot infer from the code: which collections are legacy, which service owns which table, the naming convention that is not enforced anywhere, the migration that must not be re-run. Keep it short — instructions loaded into every request compete with the actual task for attention, and a long file is one an assistant skims.

And keep the boundary in mind: this file shapes what an assistant writes. It has no bearing on what an agent connected to your database may do — that is decided by the credential it carries, and nothing in Markdown changes it. See the MCP server’s credential model.