System Prompts & Rules Files

50 min intermediate Lesson 6

Learning Outcomes

  • Explain the difference between system prompts and user prompts
  • Create effective CLAUDE.md and .cursorrules files for your projects
  • Write persistent rules that improve AI output across every interaction
  • Version and evolve rules files as your project matures
  • Share rules across teams for consistent AI-assisted development

Lesson Plan

Segment Duration Topic
Intro 4 min What are system prompts?
Explain 8 min System vs user prompts — the hierarchy
Demo 10 min Writing a CLAUDE.md file
Demo 8 min Creating .cursorrules
Explain 7 min Principles for effective persistent rules
Demo 7 min Versioning and evolving rules
Demo 4 min Team sharing patterns
Wrap-up 2 min Key takeaways

Before You Begin

Pre-work:

  • Complete Lesson 5 on Constraints & Guardrails
  • Have a coding project (any language, any size)
  • Familiarise yourself with your preferred AI tool's configuration format

Shopping List:

  • An AI coding tool (Claude Code, Cursor, Codex, or similar)
  • A project repository with at least a few files
  • A text editor for creating configuration files
  • Basic understanding of YAML/Markdown formatting

1 Understanding System Prompts

A system prompt is a set of instructions that persists across every interaction with your AI tool. Unlike a user prompt (which you type fresh each time), a system prompt operates in the background — shaping behaviour, enforcing conventions, and providing context automatically.

The hierarchy of prompts:

┌─────────────────────────────────┐
│  Tool's Built-in System Prompt  │  ← Set by the AI tool vendor
├─────────────────────────────────┤
│  Your Project Rules File        │  ← You control this
├─────────────────────────────────┤
│  Your User Prompt               │  ← What you type each time
└─────────────────────────────────┘

Each level can influence, refine, or override the level above it — though the built-in system prompt typically has the strongest authority.

Why system prompts matter:

Without a system prompt, you repeat yourself constantly:

> Use TypeScript. Follow our naming conventions. Don't use any 
> deprecated APIs. Return errors as Result types not exceptions...

With a system prompt, these instructions apply automatically to every interaction.

Where system prompts live by tool:

Tool File Location
Claude Code CLAUDE.md Project root
Cursor .cursorrules Project root
GitHub Copilot .github/copilot-instructions.md .github/ directory
Codex AGENTS.md Project root
Windsurf .windsurfrules Project root
NOTE
Key Insight
Despite different filenames, all these tools follow the same principle: a project-level file that provides persistent context and rules. The techniques in this lesson apply universally.

2 Creating Your First Rules File

Let's build a rules file from scratch. We'll use generic principles that translate across tools.

Start with project identity:

# Project: TaskFlow API

A REST API for task management built with Node.js and Express.
Uses PostgreSQL for persistence and Redis for caching.

## Tech Stack
- Runtime: Node.js 20
- Framework: Express 4.x
- Database: PostgreSQL 15 with Knex query builder
- Cache: Redis 7
- Testing: Jest + Supertest
- Language: TypeScript 5.x (strict mode)

Add coding conventions:

## Coding Conventions

- Use named exports, not default exports
- Prefer async/await over .then() chains
- All functions must have explicit return types
- Use Result<T, E> pattern for operations that can fail
- Error messages must be user-friendly (no stack traces in responses)
- Database queries go in repository files, not controllers

Add architectural rules:

## Architecture

- Follow the controller → service → repository pattern
- Controllers handle HTTP concerns only (parsing, status codes)
- Services contain business logic
- Repositories handle data access
- Never import a repository directly into a controller

Add testing expectations:

## Testing

- Every new endpoint needs integration tests
- Use factory functions for test data (see tests/factories/)
- Mock external services, never the database
- Test file naming: [module].test.ts
TIP
Tip
Start small. A rules file with 10 clear rules is better than one with 50 vague guidelines. You can always add more as patterns emerge.

Try it now: Create a rules file for your own project. Start with just three sections: project identity, tech stack, and your top 5 coding conventions.


3 Writing Rules That Actually Work

Not all rules are created equal. Here's what makes the difference between rules that get followed and rules that get ignored.

Principle 1: Be specific, not aspirational

Bad:

- Write clean code
- Follow best practices
- Keep things simple

Good:

- Functions must be under 30 lines
- No more than 3 parameters per function (use an options object for more)
- Every public function needs a JSDoc comment with @param and @returns

Principle 2: Show, don't just tell

Bad:

- Use proper error handling

Good:

- Wrap external API calls in try/catch and return a Result type:
  ```typescript
  async function fetchUser(id: string): Promise<Result<User, ApiError>> {
    try {
      const response = await api.get(`/users/${id}`);
      return Ok(response.data);
    } catch (error) {
      return Err(new ApiError('Failed to fetch user', error));
    }
  }

**Principle 3: Explain the "why" for non-obvious rules**

Bad:
```markdown
- Never use `any` type

Good:

- Never use `any` type (breaks type safety across the codebase; 
  use `unknown` and narrow with type guards instead)

Principle 4: Include anti-patterns explicitly

## Do NOT
- Don't use ORM lazy loading (causes N+1 queries in production)
- Don't throw errors for expected cases (user not found is not an error)
- Don't add comments that restate the code (// increment counter)
- Don't use barrel files (index.ts re-exports) — they break tree shaking

Principle 5: Reference existing code as examples

## Examples
- For a well-structured controller, see src/controllers/auth.controller.ts
- For test patterns, see tests/integration/users.test.ts
- For database migrations, see migrations/002_add_teams.ts
WARNING
Watch Out
Avoid contradictory rules. If you say 'keep functions short' but also 'avoid creating too many small functions', the AI will be confused. Pick a clear stance.

4 Versioning and Evolving Rules

Your rules file is a living document. It should evolve as your project grows and as you learn what works.

Track your rules in version control:

Your rules file belongs in git, right alongside your code. This means:

  • Changes to rules are reviewed in PRs just like code changes
  • You can see when and why a rule was added (git blame)
  • Different branches can experiment with different rules
  • New team members get the rules automatically when they clone

When to add a new rule:

Add a rule when you notice yourself correcting the AI for the same thing more than twice:

Session 1: "No, don't use default exports here"
Session 2: "I said no default exports"
Session 3: → Add to rules file: "Use named exports, not default exports"

When to remove or relax a rule:

Remove rules that are:

  • No longer relevant (deprecated library references)
  • Too restrictive (blocking legitimate patterns)
  • Already enforced by linting (no need for AI to double-check what ESLint catches)

Structuring rules for growth:

# CLAUDE.md

## Core Rules (always apply)
[Your most important, stable conventions]

## Current Sprint Context
[Temporary rules for the current work — remove after the sprint]
- We're migrating from Express to Fastify — all new routes use Fastify
- The payments module is being rewritten — don't modify legacy payment code

## Module-Specific Rules
### API Layer
[Rules for src/api/]

### Database Layer  
[Rules for src/db/]

Using dated comments:

## Rules

- Use Zod for all input validation (added 2024-03, replaced Joi)
- All new API endpoints must include OpenAPI annotations (added 2024-06)
TIP
Tip
Review your rules file monthly. If a rule hasn't been relevant in 30 days, consider whether it's still pulling its weight or just adding noise.

5 Team Sharing Patterns

Rules files become even more powerful when shared across a team. Everyone's AI assistant produces consistent output.

Pattern 1: Shared project rules (committed to repo)

The simplest approach — one rules file in the repository root:

project/
├── CLAUDE.md          ← Shared rules for Claude Code users
├── .cursorrules       ← Shared rules for Cursor users
├── .github/
│   └── copilot-instructions.md  ← Shared rules for Copilot users
└── src/

All team members get the same rules. Changes go through PR review.

Pattern 2: Layered rules (shared + personal)

Many tools support multiple rule files that layer on top of each other:

~/.claude/CLAUDE.md              ← Your personal global preferences
project/CLAUDE.md                ← Team project rules
project/src/api/CLAUDE.md        ← Module-specific rules

Personal preferences go in your global config (not committed):

# ~/.claude/CLAUDE.md (personal, not in git)
- I prefer verbose variable names over abbreviations
- Always explain your reasoning before writing code
- I'm learning Rust — add helpful comments in Rust files

Pattern 3: Template starters for new projects

Create a template rules file your team copies into new projects:

# [Project Name]

## Overview
[One paragraph describing the project]

## Tech Stack
[List the core technologies]

## Conventions
[Fill in your team's standards]

## Testing
[Your testing expectations]

## Security
- Never log sensitive data (tokens, passwords, PII)
- Always validate input at the boundary
- Use parameterised queries (no string concatenation in SQL)

Pattern 4: Rules for specific workflows

Different rules for different types of work:

## When writing new features
- Start with types/interfaces before implementation
- Create tests alongside the implementation
- Add migration if schema changes are needed

## When fixing bugs
- Write a failing test first that reproduces the bug
- Fix the bug
- Verify the test passes
- Check for similar bugs nearby

## When refactoring
- Never change behaviour (tests must still pass with no modifications)
- Make atomic commits (one refactoring step per commit)
- If a refactor requires test changes, it's not a pure refactor

Getting team buy-in:

  1. Start with a minimal rules file (core conventions only)
  2. Let the team see it working for a sprint
  3. Collect suggestions in a shared document
  4. Review and add rules together
  5. Remove rules that cause friction
NOTE
Key Insight
The best rules files are written collaboratively. When everyone contributes, everyone follows them. Imposed rules without buy-in often get ignored or worked around.

Questions & Answers

Q: How long should a rules file be?
Aim for 50-150 lines for a project rules file. If you go over 200 lines, consider splitting into module-specific files. The AI reads the entire file on every interaction, so very long files can dilute important rules.
Q: Should I include things that my linter already checks?
Generally no. If ESLint or Prettier already enforces a rule, the AI's output will get auto-fixed anyway. Focus your rules file on things linters can't check: architectural patterns, naming conventions for domain concepts, workflow preferences.
Q: Can rules conflict with the AI tool's built-in behaviour?
Yes, and your rules usually win for code generation decisions. However, safety-related built-in behaviour (refusing to write malware, etc.) cannot be overridden by project rules, which is by design.
Q: Should I have separate rules files for each AI tool?
If your team uses multiple tools, yes — but keep the content synchronised. The core rules should be identical; only the filename and any tool-specific syntax differs. Some teams maintain a single source of truth and generate tool-specific files from it.

Key Takeaways

  1. System prompts persist — they shape every interaction without you repeating yourself
  2. Be specific — vague rules get ignored; concrete rules with examples get followed
  3. Show the "why" — non-obvious rules need context or they'll be overridden
  4. Version your rules — commit them to git, review changes in PRs
  5. Evolve continuously — add rules when patterns emerge, remove rules that cause friction
  6. Share with your team — consistent AI output comes from consistent rules

Next Steps: In Lesson 7 — Chain-of-Thought Prompting, you'll learn how to guide AI through complex reasoning by explicitly requesting step-by-step thinking.