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 ← pointerEach pointer file is three lines:
# Rebase AI RulesPlease 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.
Why a pointer rather than a copy
Section titled “Why a pointer rather than a copy”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.
What ai-instructions.md starts with
Section titled “What ai-instructions.md starts with”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:
- Schema as code. Collections are defined in
config/collections/. Never hand-edit the generated Drizzle schema or the Postgres tables — see Schema as Code. - Applying a collection change depends on the database. While
pnpm devis running, saving the collection file is the whole step: boot regenerates the Drizzle schema and creates the missing tables and columns.pnpm db:pushis 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 ispnpm db:generatethenpnpm db:migrate. Since 0.25 On 0.24,pnpm db:pushneeds your own PostgreSQL, and the file says so. - Use the SDK. Go through
rebase.dataAsAdmin.<slug>for work done as the service identity, orgetDriver(c)inside a function when the read should run as the caller. The server client has no plaindataaccessor. Raw SQL and direct Drizzle calls bypass validation, callbacks and RLS. - Guard every custom route. Routes in
backend/functions/are mounted without authentication. UserequireAuth/requireAdminfrom@rebasepro/server/functionsin the route’s own middleware slot — readingc.get("user")is not a guard, and neither isapp.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.
Making it yours
Section titled “Making it yours”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.