Enforcing CLAUDE.md
How not to have a dead letter coding constitution
The constitution of my active project is a 2000-word document. Without it, the project would’ve descended into anarchy. I toil to enforce it among my coding agents for one reason: the pleasure of good architecture, solid security and the ability to understand generated code.
A constitution should codify at least 3 things.
Key design patterns
Directory structure
Layer responsibilities (slice your application in layers stacked on top of each other)
Each is self-explanatory and Claude Code will generate decent rules for each if you arm it with a PRD. I’ve included an example constitution structure at the end. Now, to the fun stuff.
An unwitting lawbreaker
The presence of a constitution is hardly enough. Claude Code can regurgitate 60 of them per hour. It already leaves behind comment upon comment in any file it touches, writing guidelines for future self. In plan mode, it stuffs docs with a 300-line file for every feature you implement, each file oblivious to the rules and conventions preserved in its peers.
Claude Code is a hoarder of notes, rules and plans. Like any human hoarder (Obsidian folks, raise your hands), what it hoards remains vaulted and unused unless you hold it accountable. Our constitution faces the same risk and as engineers, we must prevent it from becoming Claude’s failed Ulysses pact.
With this understanding, here is a pocket guide to effective enforcement of the constitution.
Enforce!
Once your PR is ready, open a fresh context and ask Claude to compare git diff with CLAUDE.md and record all misalignments in an empty ISSUES.json file. Each misalignment should have five properties.
{
summary: “One liner summary of the issue”,
description: “Description including rationale for why this is an issue”,
files: [”List of files affected”]
stepsToFix: [”Steps to resolution”],
fixed: false
}Claude is endowed with many quirks, 3 of which are currently relevant:
For long-running tasks, it quits on a whim and marks the task complete.
It generates many false positives when finding problems.
It suggests overengineered solutions to simple problems.
Combat all 3 by doing this:
Don’t trust Claude. Run the prompt to append issues to
ISSUES.json2-3 times or more depending on the PR’s scale.Make sure an issue is recorded once.
Don’t trust Claude.
In a fresh context, invoke Claude to triage all issues and prune the non-issues (It will specify a makeshift criteria itself).
Manually review the issues and strike out obvious false positives.
Don’t trust Claude. Skim read the file to ensure there are no over-the-top solutions.
So yeah, don’t trust Claude.
There would still be issues left that you’d mark WONTFIX in the pre-AI world. Strike them because each fixed issue results in more change which increases the machine and mental resources required to keep them in line with CLAUDE.md.
It’s time to start Claude on the fixes. If there are many issues, use a ralph wiggum loop (beware: it burns through usage limits). Alternatively, split issues into batches of 5 and tackle each batch manually in a fresh context window.
You now have a new set of changes to vet against your constitution. Don’t worry, I’m not asking you to do the whole nine yards again. Do a single prompt and fix any glaring issues. Rome wasn’t built in a single PR.
Example structure
# CLAUDE.md - Lawgbook Codebase Guide
## Technology Stack
...
## Directory Structure
...
### Generic Pattern
app/
├── main.py # FastAPI app with lifespan context
├── core/
│ └── config.py # Pydantic Settings configuration
├── db/
│ ├── database.py # Async engine, session factory, get_session()
│ └── models.py # All ORM models
├── routes/ # API endpoints
│ ├── {domain}.py # Simple domain routes
│ └── {domain}/ # Complex domain with multiple files
│ ├── auth.py # OAuth/auth endpoints
│ ├── webhook.py # Webhook handlers
│ └── utils.py # Route-specific helpers
├── schemas/ # Pydantic DTOs (request/response)
│ ├── common.py # DTOBase, shared mixins
│ └── {domain}.py # Domain-specific schemas
├── services/ # Business logic
│ └── {domain}/ # Domain service module
│ ├── service.py # Main facade class ({Domain}Service)
│ ├── auth.py # Auth/token management
│ ├── http_client.py # HTTP client with retries
│ └── {operation}.py # Specific operations (sync, push, etc.)
└── prompts/ # AI/LLM prompt templates
## Layer Responsibilities
### Routes (`app/routes/`)
**DO:**
- Define API endpoints and HTTP methods
- Validate request parameters via Pydantic DTOs
- Call service methods and return DTO instances (not dictionaries)
- Handle HTTPException mapping (ValueError → 400, etc.)
- Dependency injection (get_current_user, get_session)
**DON’T:**
- Contain business logic
- Execute database queries directly
- Call external APIs directly
- Transform data beyond DTO conversion
- Return dictionaries when DTO instances are available (use DTOs for type safety and validation)
...
## Key Design Patterns
### 1. Modular Service Architecture (Facade Pattern)
Services compose specialized managers:
class ClioService:
def __init__(self):
self._auth = ClioAuthManager()
self._http = ClioHTTPClient()
self._sync = ClioSyncManager()
self._push = ClioPushManager()
### 2. DTO Separation
- Routes accept/return Pydantic DTOs from `app/schemas/`
- Services work with ORM models internally, but return Pydantic DTOs
- Never pass ORM models directly to/from API layer
- Routes should return DTO instances, not dictionaries (even though FastAPI accepts both)
...
## Coding Conventions
...
## Route Patterns
...
## Schema Patterns
...
## Configuration
...
## Testing
...
## Logging
...
## Linting
...

