DEV Community

Cover image for How I Use .claude/rules/ to Give Claude Code Domain Knowledge About My Project's File Structure
Thomas Landgraf
Thomas Landgraf

Posted on

How I Use .claude/rules/ to Give Claude Code Domain Knowledge About My Project's File Structure

You know that moment when you ask Claude Code to edit a file and it treats your carefully structured project directory like a random pile of Markdown? It adds implementation details to a specification file. It puts a requirement under the wrong feature. It invents an ID format you never asked for.

The problem isn't that the AI is dumb. It's that it has no idea what your files mean.

I've been building a VS Code extension called SPECLAN that manages layered specifications as Markdown files with YAML frontmatter. The speclan/ directory in any project has a well-defined structure — entity types, ID schemes, status lifecycles, nesting rules. And Claude Code kept stepping on all of them until I discovered the paths frontmatter in .claude/rules/.

The problem: Claude doesn't know your conventions

My project has a directory like this:

speclan/
├── goals/           G-###-slug.md
├── features/        F-####-slug/F-####-slug.md
│   ├── requirements/  R-####-slug/R-####-slug.md
│   │   └── change-requests/  CR-####-slug.md
│   └── change-requests/  CR-####-slug.md
└── templates/
Enter fullscreen mode Exit fullscreen mode

Every file is Markdown with YAML frontmatter. IDs are random, not sequential. Features nest recursively. Requirements always belong to exactly one feature. Status goes draft → review → approved → in-development → under-test → released → deprecated. Only approved specs can be implemented. Locked specs need a ChangeRequest to modify.

None of this is obvious from the files alone. Without guidance, Claude will:

  • Create sequential IDs (F-0001, F-0002) instead of random ones
  • Put requirements at the wrong nesting level
  • Mix implementation concerns into specification files
  • Skip required frontmatter fields
  • Ignore the status lifecycle entirely

The solution: path-scoped rules

Claude Code loads .claude/rules/*.md files as persistent context. That alone is useful for project-wide conventions. But the feature that makes it powerful for structured directories is the paths frontmatter:

---
paths:
  - "speclan/**/*.md"
---
Enter fullscreen mode Exit fullscreen mode

This tells Claude Code: "Only inject these rules when I'm working with files that match this glob pattern." The rules file is invisible when you're editing TypeScript, writing tests, or doing anything else. But the moment you touch a file under speclan/, it kicks in.

Rules without a paths field load unconditionally — they're the equivalent of putting instructions in CLAUDE.md. Rules with paths only activate when Claude reads files matching the pattern. That distinction is what makes them useful for domain-specific knowledge.

What goes in the rules file

Here's the actual rules file I use (condensed — the real one is ~96 lines):

---
paths:
  - "speclan/**/*.md"
---

# SPECLAN Specification Rules

## Entity Hierarchy

Goal (G-###) → Feature (F-####) → Requirement (R-####)

ChangeRequest (CR-####) modifies locked entities.

## Directory Structure

speclan/
├── goals/           G-###-slug.md
├── features/        F-####-slug/F-####-slug.md (self-named dirs, recursive)
│   ├── requirements/  R-####-slug/R-####-slug.md
│   │   └── change-requests/  CR-####-slug.md
│   └── change-requests/  CR-####-slug.md
└── templates/<entityType>/  UUID-slug.md

## Frontmatter (YAML)

All specs are Markdown with YAML frontmatter. Required fields:
id, type, title, status, owner, created, updated

## ID Rules (NON-NEGOTIABLE)

- Goal: G-### (3 digits)
- Feature: F-#### (4 digits)
- Requirement: R-#### (4 digits)
- ChangeRequest: CR-#### (4 digits)
- IDs are random, not sequential
- Check collisions before creation

## Status Lifecycle

draft → review → approved → in-development → under-test → released → deprecated

Only approved specs can be implemented.
Locked statuses (approved+) require a ChangeRequest for modifications.

## Invariants

1. Requirements belong to exactly one Feature
2. Features may have sub-features AND requirements
3. ChangeRequests reference exactly one parent

IMPORTANT: files under speclan/ are specifications that tell
WHAT from user perspective, not HOW from developer perspective
Enter fullscreen mode Exit fullscreen mode

That last line is the most important one. It's the semantic boundary that prevents Claude from mixing concerns. Without it, you ask for a new requirement and get implementation pseudocode.

Why this works better than CLAUDE.md alone

You could put all of this in your project's CLAUDE.md. I actually did that first. The problem is context pollution — when Claude is editing a React component, it doesn't need to know about SPECLAN's ID scheme. And when it's editing a spec file, it doesn't need your TypeScript lint rules.

Path-scoped rules solve this cleanly:

  • Focused context — rules only activate when relevant
  • No noise — Claude's context window isn't cluttered with irrelevant conventions
  • Composable — you can have multiple rules files for different parts of your project

The rules file acts like a domain expert sitting next to Claude, whispering "that's a specification file, here's how they work" exactly when it matters.

Designing a good rules file

After iterating on this for a few months, here's what I've found works:

Be declarative, not procedural. Don't write step-by-step instructions. Describe the structure, the constraints, the invariants. Claude is good at applying constraints if you state them clearly.

Mark hard boundaries. I use (NON-NEGOTIABLE) for rules that must never be violated — like the ID format. Claude respects this surprisingly well.

Include the "why" for non-obvious rules. "IDs are random, not sequential" needs the implicit why: collision avoidance across branches and contributors. "Files tell WHAT not HOW" needs no explanation but does need emphasis.

Keep it under 100 lines. This is context that gets injected into every relevant interaction. If your rules file is 500 lines, you're eating into the context window Claude needs for actual work. Compress ruthlessly. Tables over prose. ASCII trees over paragraphs. The official docs recommend targeting under 200 lines for any CLAUDE.md file — for path-scoped rules, I'd argue even tighter is better.

Quote your glob patterns. This is a gotcha that'll bite you: YAML treats * and { as reserved indicators. Always quote your patterns — "**/*.ts" not **/*.ts. Unquoted patterns can silently fail.

Use brace expansion for related types. Instead of listing patterns separately, combine them: "src/**/*.{ts,tsx}" matches both TypeScript and TSX files in one pattern. Same works for directories: "{src,lib}/**/*.ts".

Test it by asking Claude to create something. After writing the rules file, ask Claude to "create a new requirement for feature F-1234." If it gets the file path, ID format, frontmatter, and directory nesting right on the first try — your rules file works.

Beyond project directories: glob patterns for other domains

The speclan/**/*.md pattern is one application. The same mechanism works for any file pattern where Claude needs domain context. Here's what I use across my NX monorepo:

Test files (`/.spec.ts`)* — inject your testing conventions: which frameworks, which patterns, how to mock, what not to test. I have rules for Jest vs Mocha conventions since my project uses both (libraries vs VS Code extension).

Webview files (`/webview/`) — inject your browser-context constraints: no Node APIs, specific CSS framework rules, message-passing protocols between webview and extension host.

Infrastructure files (`cdk//.ts`)* — inject your CDK conventions, naming standards, tagging policies, security guardrails. Claude loves to create overly permissive IAM roles unless you tell it not to.

Security-sensitive code (`src/auth//, src/payments//`)** — guardrails for sensitive areas: never log tokens, always parameterize queries, validate all inputs at function boundaries. These rules are especially valuable because the cost of Claude getting them wrong is high.

Database migrations (`prisma/migrations//`)* — safety rules: always include rollback instructions, never delete columns in the same migration that removes the code using them, add columns as nullable first.

The pattern is always the same: you have files where the semantics aren't obvious from the syntax, and you need Claude to understand the domain rules before touching them.

Tips from the trenches

A few more things I've learned from running 12+ rules files across a monorepo:

One concern per file. A testing.md shouldn't also contain API design guidelines. Separation of concerns applies to instructions just as much as code. Descriptive filenames like api-validation.md beat rules1.md.

Subdirectories work. All .md files are discovered recursively, so you can organize rules into frontend/, backend/, infra/ subdirectories. No configuration needed.

Symlinks for shared rules. If you maintain coding standards across multiple projects, symlink a shared rules directory: ln -s ~/company-standards .claude/rules/shared. Circular symlinks are handled gracefully.

User-level rules for personal preferences. Put rules in ~/.claude/rules/ for things that apply to everything you work on — your preferred commit message format, your testing style, your debugging workflow. These load before project rules, so project rules can override them.

Don't duplicate between CLAUDE.md and rules. If a convention is path-specific, put it in .claude/rules/ with a paths field. If it's truly universal (build commands, project architecture), keep it in CLAUDE.md. Conflicting instructions across files get resolved arbitrarily — not what you want for your ID scheme.

Check what's loaded with /memory. When something isn't being respected, run /memory to see which rules files Claude actually has in context. If your file isn't listed, the glob pattern isn't matching.

The compound effect

One rules file doesn't feel like much. But once you have 3-4 of them covering different parts of your project, Claude starts behaving like a developer who actually read the architecture docs. It stops guessing and starts following your conventions. The number of "no, that's not how we do it" corrections drops dramatically.

I think of .claude/rules/ files as executable documentation. They serve double duty: they document your conventions for human readers and they enforce those conventions when AI touches the code. That's a pretty good return on 50-100 lines of Markdown.


I'm the creator of SPECLAN, a VS Code extension for managing layered specifications as Markdown files in Git. The path-scoped rules described here are how I keep Claude Code aligned with SPECLAN's file structure conventions — but the technique works for any project with well-defined directory semantics.

Top comments (0)