DEV Community

daylay92
daylay92

Posted on

Context Engineering: Designing AI Systems That Actually Understand Your Codebase

Context Loading Flow

A practical guide to designing AI-ready codebases that actually understand your project.


The Frustration That Started It All

"Create a lease activation endpoint."

I typed this instruction to Claude for the 47th time. I know it was the 47th because I'd started keeping a tally in frustration.

Each time, I'd watch it generate code that looked right but missed critical details:

  • Forgot about our 8-state lease lifecycle
  • Skipped tenant isolation (hello, security vulnerability)
  • Missed the side effects (update unit status, generate invoice, log activity)
  • Used patterns inconsistent with the rest of the codebase

So I'd correct it. Explain the state machine. Remind it about organization_id. Paste in example code from other files. Twenty minutes later, we'd have working code.

The next day? Same conversation. Same corrections. Same twenty minutes burned.

I estimated I was losing 6-8 hours per week just re-explaining context. That's a full workday, every week, saying the same things to an AI that couldn't remember yesterday.

Something had to change.


The Failed Experiments

Before I found what worked, I tried several approaches that didn't:

Attempt #1: The mega-prompt. I wrote a 3,000-word system prompt covering everything. Result? Claude got confused, cherry-picked random details, and the token cost was brutal. The AI couldn't distinguish what mattered for the current task.

Attempt #2: Code comments everywhere. I added extensive JSDoc comments and inline documentation. But Claude still couldn't see the big picture—it would read one file and miss how it connected to everything else.

Attempt #3: A single massive README. 50 pages of documentation in one file. Claude would quote irrelevant sections while missing the specific thing I needed. It's like giving someone an encyclopedia when they asked for directions.

The breakthrough came when I stopped thinking about documentation for humans and started thinking about documentation for AI consumption.


The Aha Moment

Week six of the project. I was explaining the lease state machine for the dozenth time when I noticed something: I always explained it the same way. Same states, same transitions, same edge cases.

What if I just... wrote it down once? In a format optimized for Claude to parse quickly?

I created a simple markdown file:

## State Transitions
DRAFT → PENDING_APPROVAL, CANCELLED
PENDING_APPROVAL → APPROVED, DRAFT (rejected)
APPROVED → ACTIVE, CANCELLED
ACTIVE → EXPIRED, TERMINATED, RENEWED
Enter fullscreen mode Exit fullscreen mode

Next time I needed lease work, I told Claude: "Read this file first, then implement the activation endpoint."

It worked. First try. No corrections.

That file became the seed of a system that would eventually grow to 90+ files—and save me an estimated 300+ hours over the next three months.


What is Context Engineering?

Context Engineering is the practice of designing documentation systems that give AI assistants deep, persistent understanding of your codebase.

Think of it like this: instead of training a new developer from scratch every morning, you hand them a comprehensive onboarding guide that contains everything they need to be productive immediately.

The key insight is that AI assistants don't need less documentation—they need structured documentation designed for quick comprehension and targeted loading.

The Three Principles

  1. Layered Depth: Not everything needs full context. Simple tasks need simple context; complex tasks need comprehensive context.

  2. Targeted Loading: Load only what's relevant to the current task. Token budgets are real constraints.

  3. Single Source of Truth: Define patterns once, reference everywhere. No more "which version is correct?"


The Three-Tier Architecture

After weeks of iteration (and plenty of wrong turns), I settled on a three-tier system:

         ┌─────────────────────────────┐
         │      Quick Reference        │  ~100 lines
         │   (Fields, Enums, APIs)     │  Load for simple tasks
         └──────────────┬──────────────┘
                        │
         ┌──────────────▼──────────────┐
         │      Domain Context         │  ~400 lines
         │  (State Machines, Rules)    │  Load for complex features
         └──────────────┬──────────────┘
                        │
         ┌──────────────▼──────────────┐
         │     Pattern Guides          │  ~300 lines
         │  (How to implement)         │  Load for new code
         └─────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Three-Tier Architecture

Tier 1: Quick Reference Files (~100 lines)

These are optimized for fast lookups. Entity fields, enums, API signatures, basic relationships. When I just need to know "what are the lease statuses?" I load the quickref—nothing more.

# Leasing Quick Reference

## Entity: Lease

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| id | UUID | auto | PK |
| lease_number | string(50) | auto | Format: LS-{YYYY}-{SEQ} |
| status | enum | yes | LeaseStatus |
| start_date | date | yes | |
| end_date | date | yes | Must be > start_date |
| rent_amount | decimal(12,2) | yes | |
| security_deposit | decimal(12,2) | yes | Default: 0 |

## Enums

enum LeaseStatus {
  DRAFT, PENDING_APPROVAL, APPROVED, ACTIVE,
  EXPIRED, TERMINATED, RENEWED, CANCELLED
}

## State Transitions (Summary)

DRAFT → PENDING_APPROVAL, CANCELLED
PENDING_APPROVAL → APPROVED, DRAFT
APPROVED → ACTIVE, CANCELLED
ACTIVE → EXPIRED, TERMINATED, RENEWED

## API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | /leases/:id/activate | Activate lease |
| POST | /leases/:id/terminate | Terminate lease |
| POST | /leases/:id/renew | Renew lease |
Enter fullscreen mode Exit fullscreen mode

~95 lines. Everything Claude needs for simple CRUD work. Nothing it doesn't.

Tier 2: Domain Context Files (~400 lines)

These contain the full business logic. State machine diagrams, transition rules, workflow implementations, validation logic. When I'm implementing a complex feature, I load the domain file.

# Leasing Domain - Full Context

## State Machine

### State Diagram

                    ┌──────────────┐
                    │    DRAFT     │
                    └──────┬───────┘
                           │ submit()
                           ▼
            ┌──────────────────────────────┐
            │      PENDING_APPROVAL        │
            └──────┬────────────┬──────────┘
                   │            │
          approve()│            │reject()
                   ▼            ▼
            ┌──────────┐  ┌──────────┐
            │ APPROVED │  │  DRAFT   │
            └────┬─────┘  └──────────┘
                 │ activate()
                 ▼
            ┌──────────┐
            │  ACTIVE  │
            └────┬─────┘
    ┌────────────┼────────────┐
    ▼            ▼            ▼
┌────────┐ ┌──────────┐ ┌─────────┐
│EXPIRED │ │TERMINATED│ │ RENEWED │
└────────┘ └──────────┘ └─────────┘

## Lease Activation Workflow

async activate(tenant, leaseId) {
  return this.dataSource.transaction(async (manager) => {
    // 1. Fetch with tenant isolation (CRITICAL)
    const lease = await manager.findOne(Lease, {
      where: {
        id: leaseId,
        organization_id: tenant.organizationId  // Never skip this
      },
      relations: ['unit', 'tenant'],
    });

    // 2. Validate state transition
    if (lease.status !== LeaseStatus.APPROVED) {
      throw new BusinessRuleException('INVALID_STATUS',
        `Cannot activate lease in ${lease.status} status`);
    }

    // 3. Check for overlapping leases on same unit
    const overlapping = await this.checkOverlappingLeases(
      lease.unit_id, lease.start_date, lease.end_date, lease.id
    );
    if (overlapping) {
      throw new BusinessRuleException('LEASE_OVERLAP',
        'Unit has overlapping lease');
    }

    // 4. Update lease
    lease.status = LeaseStatus.ACTIVE;
    lease.activated_at = new Date();
    await manager.save(lease);

    // 5. Update unit status (side effect)
    await manager.update(Unit, lease.unit_id, {
      status: UnitStatus.OCCUPIED,
      current_lease_id: lease.id,
      current_tenant_id: lease.tenant_id,
    });

    // 6. Generate first invoice (side effect)
    await this.invoiceService.generateForLease(lease, manager);

    // 7. Log activity (side effect)
    await this.activityService.log({
      entity_type: 'lease',
      entity_id: lease.id,
      action: 'ACTIVATED',
      actor_id: tenant.userId,
    }, manager);

    // 8. Queue notification (side effect)
    await this.notificationService.queue({
      type: NotificationType.LEASE_ACTIVATED,
      recipient_id: lease.tenant_id,
    });

    return lease;
  });
}
Enter fullscreen mode Exit fullscreen mode

Now Claude understands not just what to build, but how it should work—including all five side effects that my early attempts kept missing.

Tier 3: Pattern Guides (~300 lines)

These are implementation how-tos. API structure, database patterns, testing approaches. Framework-specific enough to be useful, but focused on our conventions.

# API Patterns

## Controller Structure

@Controller('resources')
export class ResourceController {
  @Get()
  @Permissions('resource:view')
  async findAll(
    @Tenant() tenant: TenantContext,  // Always inject tenant
    @Query() query: QueryDto,
  ): Promise<Paginated<ResourceDto>> {
    return this.service.findAll(tenant, query);
  }

  @Post(':id/activate')
  @Permissions('resource:activate')
  @HttpCode(HttpStatus.OK)  // Not 201 for state changes
  async activate(
    @Tenant() tenant: TenantContext,
    @Param('id', ParseUUIDPipe) id: string,
  ): Promise<ResourceDto> {
    return this.service.activate(tenant, id);
  }
}

## Error Handling

// Framework exceptions for HTTP errors
throw new NotFoundException('Lease not found');
throw new ForbiddenException('Insufficient permissions');

// Custom exception for business rules
throw new BusinessRuleException('LEASE_OVERLAP',
  'Unit already has active lease');

// Always include error code for client handling
Enter fullscreen mode Exit fullscreen mode

Frontend Projects: The UI Layer

If your project has significant frontend work, the three-tier system benefits from UI-specific additions:

         ┌─────────────────────────────┐
         │    UI Quick Reference       │  ~100 lines
         │  (Which pattern to use?)    │  Decision tree for forms, pages
         └──────────────┬──────────────┘
                        │
         ┌──────────────▼──────────────┐
         │    Focused UI Patterns      │  ~150 lines each
         │  (Forms, Modals, Tables)    │  One file per UI pattern
         └──────────────┬──────────────┘
                        │
         ┌──────────────▼──────────────┐
         │    Component Methodology    │  ~400 lines
         │  (Where components live)    │  Package structure, composition rules
         └─────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

I added these files to handle frontend complexity:

docs/patterns/ui/
├── FORM_PATTERNS.md      # Modal vs page vs wizard decision tree
├── DETAIL_PAGES.md       # Full detail vs focused detail templates
├── EMPTY_STATES.md       # Empty state patterns and copy
├── SKELETON_LOADERS.md   # Loading state patterns
└── MODALS_TOASTS.md      # Confirmation dialogs, notifications

docs/quickref/UI_PATTERNS.qr.md   # Quick decision tree
docs/patterns/COMPONENT_METHODOLOGY.md  # Where to put components
Enter fullscreen mode Exit fullscreen mode

The key insight: UI decisions are recursive. Before building a page, you need to decide which pattern. Before building a form, you need to decide modal vs page vs wizard. Having these decisions documented prevents inconsistent UX across features.

My CLAUDE.md now includes a critical rule:

### Component-First Development (CRITICAL)

**BEFORE building ANY UI feature, you MUST:**

1. Load `docs/patterns/COMPONENT_METHODOLOGY.md`
2. Decompose the design into component hierarchy
3. Check `packages/ui` for existing components
4. Build missing components in shared package FIRST
5. THEN compose the page in the app

**Import rule:**
// ✅ ALWAYS import from package
import { Button, Card } from '@myproject/ui';

// ❌ NEVER create duplicates in apps/
Enter fullscreen mode Exit fullscreen mode

This single rule eliminated the "duplicate component" problem where Claude would create a new Button in every feature folder.


The Pre-Implementation Phase

One pattern that emerged later—and should have been there from the start—is a formal clarification phase before writing code.

The problem: Claude would jump into implementation with reasonable-sounding assumptions that didn't match my actual requirements. I'd end up with working code that solved the wrong problem.

The fix: A structured question phase in CLAUDE.md:

### Pre-Implementation Clarification Phase

**For non-trivial tasks**: After loading context but BEFORE writing code,
ask clarifying questions.

**Skip for trivial tasks**: Typo fixes, single-line changes, simple renames.

#### Question Categories

| Category | Questions to Consider |
|----------|----------------------|
| **User Value & UX** | Does this solve a real user problem? Is the UX optimal? Could it be simpler? |
| **Edge Cases & Errors** | What happens at boundaries? Empty states? Max limits? Rollback needed? |
| **Conflicts & Coherence** | Does this contradict existing behavior? Fits existing patterns? |
| **Security & Performance** | Auth concerns? Data access controls? N+1 queries? Will this scale? |

#### Before Proceeding, Ensure:
- All ambiguities resolved
- Edge cases identified and handling agreed
- No contradictions with existing system
Enter fullscreen mode Exit fullscreen mode

This added maybe 2-3 minutes to each feature but saved 20+ minutes of rework. Claude now asks "Should the delete button require confirmation?" before implementing, rather than after I notice it's missing.


The Master Reference File

At the root sits CLAUDE.md—the file that's always loaded. This is your AI's "home base" containing:

  1. Project Identity: What we're building, who it's for
  2. Tech Stack: Technologies and versions (Claude needs to know if we're using NestJS vs Express)
  3. Critical Conventions: Naming patterns, file structure, import order
  4. Context Loading Guide: Which files to load for which tasks
  5. Quality Requirements: What "done" looks like
  6. Anti-patterns: What NOT to do (this section alone prevented dozens of bugs)

The crucial section is the Context Selection Matrix:

## Context Loading Guide

| Task Type | Load These Files |
|-----------|------------------|
| Quick lookup | docs/quickref/{DOMAIN}.qr.md |
| Simple CRUD | quickref + docs/patterns/API.md |
| Complex feature | docs/domains/{DOMAIN}.md + patterns/API.md |
| State machine | docs/domains/{DOMAIN}.md (required!) |
| Writing tests | docs/patterns/TESTING.md + quickref |
Enter fullscreen mode Exit fullscreen mode

This tells Claude exactly what to read before starting any task. No guessing, no missing context, no wasted tokens loading irrelevant files.


The Mistakes That Taught Me

Building this system wasn't smooth. Here are three mistakes that cost me time:

Mistake #1: Putting Everything in Tier 2

Early on, I made all my context files comprehensive 400-line domain documents. Every simple CRUD task loaded full state machines and workflow code.

The problem? Token waste and context confusion. Claude would reference complex workflow logic when I just wanted a simple GET endpoint.

The fix: Create the quickref tier. Simple questions get simple context. Complex questions get comprehensive context. Match the depth to the task.

Mistake #2: Duplicating Information Across Tiers

My first quickref files duplicated content from domain files. When I updated the state machine, I'd forget to update the quickref summary. Claude would get conflicting information.

The fix: Quickrefs now summarize and reference. At the top of every quickref: > **Full details**: docs/domains/LEASING.md. The quickref has enough for simple tasks; complex work loads the source of truth.

Mistake #3: Not Documenting Anti-Patterns

I kept seeing the same mistakes: any types, missing tenant filters, console.logs left in code. I'd correct them, but they'd reappear.

The fix: An explicit "DO NOT" section in CLAUDE.md:

## Anti-Patterns (DO NOT)

// ❌ Don't use `any`
const data: any = response;

// ❌ Don't skip tenant filtering
return this.repo.find();  // Security bug!

// ❌ Don't hardcode IDs
const adminId = '550e8400-e29b-41d4-a716-446655440000';

// ❌ Don't use console.log in production code
console.log('debug:', data);
Enter fullscreen mode Exit fullscreen mode

Negative examples are as valuable as positive ones. Maybe more.


Token Budget Management

Here's something I learned the hard way: context isn't free. Every token of context is a token that can't be used for reasoning or code generation.

I hit this wall when Claude started truncating responses mid-function. Too much context, not enough room for output.

Now I think about context loading like database queries—load only what you need:

Context Load ~Tokens Use Case
CLAUDE.md only 3,500 General questions
+ 1 quickref 5,000 Simple CRUD
+ 1 domain 8,500 Complex feature
+ 1 domain + 1 pattern 11,000 Complex new feature
+ 2 domains (cross-cutting) 13,500 Multi-domain work

My rule: Stay under 15,000 tokens to leave headroom for code generation.

This is why the three-tier system matters. I don't load full domain files for simple questions—I load the quickref. The full context exists when I need it, but I'm not paying the token cost when I don't.

Before and After Comparison


Architecture Decision Records: The "Why" Documentation

One pattern that paid off unexpectedly: Architecture Decision Records (ADRs). These document why we made specific choices.

# ADR-003: Multi-tenancy Approach

## Context
We need to isolate data between organizations...

Options considered:
1. Database per tenant
2. Schema per tenant
3. Shared schema with organization_id

## Decision
We chose **shared schema with organization_id**.

## Rationale
- Simpler infrastructure (one database)
- Easier migrations (one schema)
- Works at our scale (hundreds of orgs, not thousands)
- Team has experience with this pattern

## Consequences
- Every query MUST include organization_id filter
- Risk of cross-tenant data leaks if filter forgotten
- Added TenantContext to enforce this at framework level
Enter fullscreen mode Exit fullscreen mode

Without this ADR, Claude would suggest "why not use separate databases?" every few weeks. Now I point to the decision and its rationale. The AI understands the tradeoffs and doesn't re-litigate settled architecture.

I have ADRs for: monorepo structure, ORM choice, authentication strategy, state machine implementation, and file storage approach. Six documents that prevent dozens of circular conversations.


Before and After: The Real Difference

Let me show you what changed.

Before Context Engineering

Me: Create a lease activation endpoint

Claude: I'll create a POST /leases/:id/activate endpoint:

@Post(':id/activate')
async activate(@Param('id') id: string) {
  const lease = await this.leaseRepo.findOne(id);
  lease.status = 'ACTIVE';
  return this.leaseRepo.save(lease);
}
Enter fullscreen mode Exit fullscreen mode

What's wrong:

  • No tenant isolation (security vulnerability)
  • No state validation (can "activate" a cancelled lease?)
  • No transaction (partial updates if something fails)
  • No side effects (unit status, invoice, activity log)
  • No error handling
  • Wrong patterns (doesn't match our controller structure)

Time to fix: 20+ minutes of back-and-forth

After Context Engineering

Me: Create a lease activation endpoint
@docs/domains/LEASING.md @docs/patterns/API.md

Claude: Looking at the leasing domain, activation requires:

  1. Current status must be APPROVED (from state machine)
  2. Must check for overlapping leases
  3. Transaction wrapping lease + unit updates
  4. Side effects: unit status, invoice generation, activity log, notification

Here's the implementation following your API patterns:

// Controller
@Post(':id/activate')
@Permissions('lease:activate')
@HttpCode(HttpStatus.OK)
async activate(
  @Tenant() tenant: TenantContext,
  @Param('id', ParseUUIDPipe) id: string,
): Promise<LeaseResponseDto> {
  return this.leaseService.activate(tenant, id);
}

// Service
async activate(tenant: TenantContext, leaseId: string): Promise<Lease> {
  return this.dataSource.transaction(async (manager) => {
    const lease = await manager.findOne(Lease, {
      where: {
        id: leaseId,
        organization_id: tenant.organizationId
      },
      relations: ['unit', 'tenant'],
    });

    if (!lease) {
      throw new NotFoundException('Lease not found');
    }

    if (lease.status !== LeaseStatus.APPROVED) {
      throw new BusinessRuleException('INVALID_STATUS',
        `Cannot activate lease in ${lease.status} status`);
    }

    // Check overlapping leases
    const overlapping = await this.checkOverlappingLeases(
      lease.unit_id,
      lease.start_date,
      lease.end_date,
      lease.id,
      manager
    );

    if (overlapping) {
      throw new BusinessRuleException('LEASE_OVERLAP',
        'Unit has overlapping lease');
    }

    // Update lease
    lease.status = LeaseStatus.ACTIVE;
    lease.activated_at = new Date();
    await manager.save(lease);

    // Update unit (side effect)
    await manager.update(Unit, lease.unit_id, {
      status: UnitStatus.OCCUPIED,
      current_lease_id: lease.id,
      current_tenant_id: lease.tenant_id,
    });

    // Generate invoice (side effect)
    await this.invoiceService.generateForLease(lease, manager);

    // Log activity (side effect)
    await this.activityService.log({
      organization_id: tenant.organizationId,
      entity_type: 'lease',
      entity_id: lease.id,
      action: 'ACTIVATED',
      actor_id: tenant.userId,
    }, manager);

    // Queue notification (side effect)
    await this.notificationService.queue({
      type: NotificationType.LEASE_ACTIVATED,
      recipient_id: lease.tenant_id,
    });

    return lease;
  });
}
Enter fullscreen mode Exit fullscreen mode

Time to working code: First try. Maybe minor tweaks.

That's the difference. Not "slightly better"—categorically different. The AI understood the requirements, the patterns, the side effects, and the error handling. All from two file references.

Token Budget


Customizing for Your Project: A Walkthrough

The template is generic. Here's how to make it yours, using a SaaS billing system as an example.

Step 1: Define Your Domains

List your major feature areas:

  • Users & Authentication
  • Organizations & Teams
  • Subscriptions
  • Invoices
  • Payments

Step 2: Create a Quick Reference

Copy docs/quickref/_TEMPLATE.qr.md to docs/quickref/SUBSCRIPTIONS.qr.md:

# Subscriptions Quick Reference

> **Full details**: docs/domains/SUBSCRIPTIONS.md

## Entity: Subscription

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| id | UUID | auto | PK |
| organization_id | UUID | yes | Tenant isolation |
| plan_id | UUID | yes | FK → plans |
| status | enum | yes | SubscriptionStatus |
| current_period_start | timestamp | yes | |
| current_period_end | timestamp | yes | |
| cancel_at_period_end | boolean | yes | Default: false |

## Enums

enum SubscriptionStatus {
  TRIALING = 'TRIALING',
  ACTIVE = 'ACTIVE',
  PAST_DUE = 'PAST_DUE',
  CANCELED = 'CANCELED',
  UNPAID = 'UNPAID'
}

## State Transitions

TRIALING → ACTIVE, CANCELED
ACTIVE → PAST_DUE, CANCELED
PAST_DUE → ACTIVE, UNPAID, CANCELED
UNPAID → ACTIVE, CANCELED
CANCELED → (terminal)

## API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| POST | /subscriptions | Create subscription |
| POST | /subscriptions/:id/cancel | Cancel subscription |
| POST | /subscriptions/:id/reactivate | Reactivate canceled |
Enter fullscreen mode Exit fullscreen mode

~80 lines. Enough for Claude to handle basic subscription work.

Step 3: Add to Context Matrix

Update CLAUDE.md:

## Domain Quick Reference

| Domain | Quick Ref | Full Context |
|--------|-----------|--------------|
| Auth | quickref/AUTH.qr.md | domains/AUTH.md |
| Subscriptions | quickref/SUBSCRIPTIONS.qr.md | domains/SUBSCRIPTIONS.md |
| Invoices | quickref/INVOICES.qr.md | domains/INVOICES.md |
Enter fullscreen mode Exit fullscreen mode

Now Claude knows where to find subscription context.

Step 4: Build Domain File When Needed

You don't need full domain files immediately. Create them when you hit complex logic:

  • Implementing the renewal workflow? Time for domains/SUBSCRIPTIONS.md
  • Just doing CRUD? Quickref is enough

Build what you need, when you need it.


The Task Breakdown System

As the project grew, I needed more than context files—I needed to track what to build and in what order.

The task system has three components:

1. Master Index (docs/tasks/TASK_INDEX.md)

# Task Index

**Total**: 127 tasks | **Complete**: 0 | **In Progress**: 0

## Features

| # | Feature | Tasks | Complete | Status |
|---|---------|-------|----------|--------|
| 0 | Foundation | 18 | 0 | 🔴 Not Started |
| 1 | Portfolio | 18 | 0 | 🔴 Not Started |
| 2 | Properties | 15 | 0 | 🔴 Not Started |
| 3 | Units | 13 | 0 | 🔴 Not Started |
...

## Recommended Order
0.x Foundation → 2.x Properties → 3.x Units → 5.x Tenants → 4.x Leases
Enter fullscreen mode Exit fullscreen mode

2. Feature Task Files (docs/tasks/01-portfolio/tasks.md)

Each feature gets a task file with atomic, testable tasks:

# Portfolio Tasks

**Context**: Load `docs/quickref/PORTFOLIO.qr.md`

## Tasks

### 1.1 Create Portfolio Entity
- [ ] Define entity with fields from quickref
- [ ] Add TypeORM decorators
- [ ] Create migration
- **Tests**: Entity creates, validates required fields
- **Blocks**: 1.2, 1.3

### 1.2 Portfolio CRUD Endpoints
- [ ] GET /portfolios (list with pagination)
- [ ] GET /portfolios/:id (single with relations)
- [ ] POST /portfolios (create)
- [ ] PATCH /portfolios/:id (update)
- **Tests**: Each endpoint, validation errors, tenant isolation
- **Blocked by**: 1.1
Enter fullscreen mode Exit fullscreen mode

3. Context Routing (docs/tasks/context-slices/README.md)

Maps task types to required context files:

## Context Slices

| Task Pattern | Load These Files |
|--------------|------------------|
| `*.1` Entity tasks | DATABASE.md + quickref |
| `*.2` CRUD tasks | API.md + quickref |
| `*.3` State machine tasks | Full domain file |
| `*.4` UI tasks | COMPONENT_METHODOLOGY.md + domain |
Enter fullscreen mode Exit fullscreen mode

This system lets me (or Claude) pick up any task and know exactly:

  • What to build
  • What context to load
  • What it depends on
  • How to verify it works

Getting Started

I've packaged this system into a template repository you can fork and customize.

Quick Setup

  1. Fork the template: github.com/daylay92/context-engineering-template
  2. Customize CLAUDE.md with your project identity and tech stack
  3. Create quickref files for your main domains (start with 2-3)
  4. Add domain files as complexity demands them
  5. Set up task tracking when you're ready to build

What's Included

context-engineering-template/
├── CLAUDE.md                 # Customize this first
├── .cursorrules              # Cursor IDE variant
├── docs/
│   ├── PROGRESS.md           # Project status
│   ├── CONTEXT_INDEX.md      # Navigation guide
│   ├── quickref/
│   │   ├── _TEMPLATE.qr.md   # Copy for each domain
│   │   └── EXAMPLE.qr.md     # Annotated example
│   ├── domains/
│   │   ├── _TEMPLATE.md      # Copy when needed
│   │   └── EXAMPLE.md        # Full example with state machine
│   ├── patterns/
│   │   ├── API.md            # REST patterns
│   │   ├── DATABASE.md       # Database patterns
│   │   └── TESTING.md        # Testing patterns
│   ├── decisions/
│   │   └── _TEMPLATE.md      # ADR template
│   └── tasks/
│       ├── TASK_INDEX.md     # Master tracker
│       ├── README.md         # How to use tasks
│       ├── context-slices/   # Task → context mapping
│       └── 00-example/       # Example feature tasks
Enter fullscreen mode Exit fullscreen mode

File Structure

All templates include annotations explaining each section. Delete them once you understand the system.


The ROI

Let me be concrete about what this cost and what it returned.

Investment:

  • Initial system design: ~8 hours
  • Writing 90+ files: ~50 hours over 3 months
  • Ongoing maintenance: ~2 hours/month

Return:

  • Time saved per session: 15-25 minutes
  • Sessions per week: 15-20
  • Weekly savings: 4-8 hours
  • Three-month savings: 50-100+ hours

But the bigger win isn't time—it's quality. The code Claude generates now fits naturally into the codebase. Fewer bugs. Less refactoring. More consistency. The patterns I defined in documentation become the patterns in the code.


Quality Verification

One thing I underestimated: Claude doesn't naturally verify its work. It generates code and moves on. I added an explicit quality checklist to CLAUDE.md:

## Quality Checklist (MANDATORY)

**Before marking ANY task complete:**

☐ Type check     → pnpm typecheck        → ZERO errors
☐ Lint           → pnpm lint             → ZERO errors
☐ Format         → pnpm format           → Applied
☐ Tests pass     → pnpm test             → ALL green
☐ Tests written  → New code has tests    → See coverage rules
☐ Build works    → pnpm build            → Succeeds
Enter fullscreen mode Exit fullscreen mode

More importantly, I require Claude to output the results:

### Report Format
After completing work, output:

## Quality Checklist
- [x] Type check passed
- [x] Lint passed (0 errors)
- [x] Formatted
- [x] Tests passed (12 passed, 0 failed)
- [x] New tests: lease.service.spec.ts (8 tests)
- [x] Build succeeded
Enter fullscreen mode Exit fullscreen mode

This seemingly small addition had outsized impact. Claude now runs the checks because it knows it needs to report them. The verification step went from "often skipped" to "always done."


Conclusion

Context Engineering isn't about writing more documentation—it's about writing smarter documentation. Documentation designed for AI consumption, structured for quick loading, and organized for targeted retrieval.

The 90+ markdown files I created represent about a week of focused work, spread over three months of iteration. They've saved me hundreds of hours of repetitive explanation and correction.

But more importantly, they changed the nature of my work with AI. Instead of fighting against amnesia every session, I have a collaborator that understands my project deeply—every time, from the first message.

If you're using AI coding assistants more than a few times per week, this investment pays for itself fast. The question isn't whether to build a context system, but how soon you'll start.

Fork the template. Customize it for your project. And stop repeating yourself.


Template Repository: github.com/daylay92/context-engineering-template

Have questions or built something with this? I'd love to hear about it. Open an issue on the template repo or reach out on Twitter/X.


This article was written while building Qace Homes PMS, a property management system. The context engineering system described here evolved from real frustration into a real solution—90+ files that fundamentally changed how I work with AI.


Tags: #ai #claude #cursor #copilot #productivity #developer-tools #programming #context-engineering

Top comments (0)