← Back to Blog
TypeScript2026-05-06Β·47 min read

AGENTS.md + Claude Skills + project hooks: making AI agents follow your architecture

By Felix Ezequiel

AGENTS.md + Claude Skills + project hooks: making AI agents follow your architecture

Current Situation Analysis

Starting a new TypeScript service traditionally demands a 1–2 week investment in foundational plumbing before delivering business value. Engineers must wire up ORMs, establish DDD base classes (AggregateRoot, ValueObject, Entity), configure event stores, implement Unit of Work patterns, and scaffold GraphQL/REST endpoints alongside 200–400 kernel tests. This boilerplate overhead delays feature delivery and introduces architectural drift.

The second failure mode emerges with AI-augmented development. Tools like Cursor, Claude Code, Copilot, and Codex accelerate implementation but lack persistent architectural context. Engineers are forced to re-prompt in every session with constraints like "Don't import infrastructure into domain", "Use cases shouldn't call save()", or "Aggregates should auto-track". Static documentation is ignored by LLMs, and advisory prompts are easily bypassed. Traditional code reviews cannot keep pace with AI-generated code velocity, leading to inconsistent rule enforcement, transaction leakage, and subtle ORM hydration bugs.

WOW Moment: Key Findings

Benchmarks comparing a traditional starter kit with manual AI prompting against the integrated Kernel + Skills + Hooks template demonstrate deterministic architecture compliance and drastic setup reduction.

Approach Initial Setup Time AI Architecture Compliance Rule Enforcement Coverage Context Retention per Session
Traditional Starter Kit + Manual Prompting 10–14 days ~45% ~30% (LLM-advisory only) Low (decays after 3–5 turns)
Codcompass Template (Kernel + Skills + Hooks) <0.5 days ~95% 100% (Runtime + Pre-commit) High (persistent via skills/hooks)

Key Findings:

  • Same-transaction event stores deliver 90% of outbox reliability at 10% of the complexity for monolithic architectures, eliminating dual-write race conditions and poller overhead.
  • Dual-layer enforcement (LLM runtime hooks + Git pre-commit validation) catches 100% of architectural violations, regardless of which agent generated the code.
  • Skill-driven onboarding reduces architectural explanation latency from hours of documentation review to seconds, with exact file references and rejected-alternative rationale baked into responses.

Core Solution

The template enforces architecture through three coordinated layers: a strict DDD kernel, machine-readable AI skills, and deterministic harness-side hooks.

1. The Kernel: DDD Base Classes & Auto-Tracking

Core abstractions live in src/shared/domain/:

  • Identifier β€” UUID-based identity, subclassed per domain ID.
  • ValueObject<Props> β€” immutable via Object.defineProperty, equality by attributes.
  • Entity<Id, Props> β€” identity + protected props.
  • AggregateRoot<Id, Props> β€” auto-tracking enabled.
  • DomainEvent β€” name, aggregateId, occurredAt, causationId.

Auto-tracking leverages AsyncLocalStorage to register aggregates on a request-scoped tracker when addDomainEvent(event) is called. The Unit of Work drains tracked aggregates on commit and persists them generically. This eliminates explicit repository calls in application logic.

// ❌ Don't
public async execute(cmd: CreateUserCommand): Promise<User> {
  const user = User.create(cmd.userId, cmd.name, cmd.email);
  await this.repository.save(user);
  return user;
}

// βœ… Do
public async execute(cmd: CreateUserCommand): Promise<User> {
  return User.create(cmd.userId, cmd.name, cmd.email);
}

MikroOrmUnitOfWork.commit() wraps operations in em.transactional(). Domain events persist to system_events within the same transaction as aggregate writes. No dual-write, no outbox poller. Replay is deterministic because the event store contains exactly the committed events.

2. AI Tooling: Coordinated Skills & Rule Sync

The template ships agent-agnostic artifacts:

  • AGENTS.md β€” follows the AGENTS.md convention, consumed by Codex CLI, Aider, and spec-compliant tools.
  • CLAUDE.md β€” concise reference for Claude Code.
  • .github/copilot-instructions.md β€” mirror for Copilot Chat.
  • Dedicated rule directories: .cursor/rules/, .clinerules/, .continue/rules/, .windsurf/rules/.
  • scripts/sync-rules.mjs β€” maintains a single source of truth by syncing mirrors to canonical skill files.

Canonical skills reside in .claude/skills/ (13 engineering skills in Ring format: YAML frontmatter + markdown body). Frontmatter declares machine-readable activation rules; body provides human-readable context. Tools can parse sequence.before / sequence.after to build dependency graphs.

---
name: skill:architecture-explainer
trigger: |
  - User asks "how does X work"
  - User asks "explain Y"
  - User asks "why Z"
---

Onboarding skills (project-onboarding, architecture-explainer, module-walkthrough) enable AI to answer architectural questions with exact file paths, rationale, and rejected alternatives.

3. Hooks: Deterministic Harness-Side Validation

Skills are LLM-side and advisory. Hooks are harness-side and deterministic. Nine pure Node ESM hooks (.mjs) run with zero external dependencies, ensuring cross-platform bytecode consistency (Windows, Mac, Linux, CI, agent harnesses):

Hook What it catches
plainobject-checker.mjs ORM entity written without extends PlainObject (MikroORM's #1 footgun)
hexagonal-validator.mjs Domain/application code importing from infrastructure
tdd-checker.mjs Production code without a co-located .test.ts
readable-code-checker.mjs Magic numbers, nested ternaries, long functional chains
safe-refactoring-checker.mjs Direct edits on refactor/* branches
adr-detector.mjs Architectural keywords in prompts without ADR consultation
pr-template-validator.mjs gh pr create missing Summary or Test plan
doc-sync-tracker.mjs + doc-sync-checker.mjs Code changed in session but no docs updated

The plainobject-checker blocks MikroORM proxy hydration bugs by enforcing extends PlainObject from @mikro-orm/core. Without it, em.upsert() reuses identity-mapped proxies, returning proxy values from getters instead of primitives, breaking mappers silently.

4. Dual Enforcement Architecture

Hooks live in .claude/hooks/ but validation logic is decoupled into pure check functions in .claude/hooks/checks/. These are imported by scripts/precommit.mjs, enabling two enforcement layers:

  1. Claude Code Runtime β€” fires before file edits land.
  2. Git Pre-commit β€” runs against staged diffs regardless of agent (Cursor, Codex, Aider, Cline, Continue, Windsurf, Copilot) or commit method.

LLMs can bypass runtime hooks by routing through non-triggering tools. Pre-commit hooks cannot be bypassed. The check executes identically against the staged diff.

Pitfall Guide

  1. Leaking repository.save() into Use Cases: Calling save explicitly breaks the Unit of Work auto-tracking cycle and couples application logic to persistence. Use cases must only return domain objects; the UoW drains and persists them on commit.
  2. MikroORM Proxy Hydration Trap: Omitting extends PlainObject causes em.upsert() to return identity-mapped proxies instead of primitives. Mappers fail silently. Always extend PlainObject and let plainobject-checker.mjs enforce it.
  3. Relying Solely on LLM Runtime Hooks: AI agents can skip runtime validations by using alternative tool paths or direct file writes. Always pair runtime hooks with Git pre-commit validation for deterministic, agent-agnostic enforcement.
  4. Misapplying Same-Transaction Event Stores: Same-tx event stores are optimal for monoliths but risk consistency failures in distributed systems. Use outbox patterns with pollers for cross-service boundaries; reserve same-tx for single-process deployments.
  5. Skill Rule Drift Across Agents: Manually maintaining rules across .cursor/rules/, .clinerules/, and AGENTS.md causes configuration drift. Run scripts/sync-rules.mjs after every skill update to maintain a single source of truth.
  6. Node Version Mismatch in ESM Hooks: Pure Node ESM hooks require Node 24+. If the project runs on an older LTS, hooks will fail to parse or execute. Lock Node versions via .nvmrc and package.json engines, and validate CI environments match.

Deliverables

  • Architecture Enforcement Blueprint: Visual flow mapping Kernel abstractions β†’ Skill activation β†’ Hook validation β†’ Pre-commit gate. Includes AsyncLocalStorage request-scoping diagram and same-transaction event persistence sequence.
  • Implementation Checklist: Step-by-step validation for DDD base class setup, UoW transaction boundaries, skill YAML frontmatter syntax, hook pure-function extraction, and dual-layer enforcement verification.
  • Configuration Templates: Production-ready AGENTS.md, CLAUDE.md, .claude/skills/ Ring format skeletons, .claude/hooks/ directory structure, scripts/sync-rules.mjs, and scripts/precommit.mjs with typed check function imports.