Arquitectura de software como contrato ejecutable
Kevin Dávila
El otro dia le pedi a Claude Code que me creara una feature nueva en un proyecto Angular con domains bien definidos. Lo que me devolvio funcionaba. Compilaba. Los tests pasaban. Pero el data access estaba dentro del componente, con referencias cruzadas entre domains.
Los coding agents escriben codigo mas rapido de lo que podemos leer. Eso es genial para velocidad y terrible para arquitectura. Por defecto, un LLM optimiza por "funciona", no por "encaja en nuestra arquitectura objetivo".
Lo bueno es que se puede describir la arquitectura de forma deterministica para que el agente no solo la conozca, sino que la cumpla. Y que reciba feedback automatico cuando no la cumple.
El problema es arquitectura que existe solo en una presentacion
Una arquitectura documentada en un slide deck no sirve. Los developers la ignoran. Los agents nunca la vieron. Y sin un mecanismo de enforcement, los boundaries se difuminan hasta que tu proyecto es un monolito disfrazado de modulos.
"By default, a language model optimizes for working code, not for adherence to your target architecture." - Source: Manfred Steyer - Angular Architects
La solucion tiene tres patas: documentar la arquitectura de forma que el agente la entienda, validarla de forma deterministica, y alimentar los errores de vuelta al agente para que se corrija solo.
Sheriff: El guardia de los boundaries
Sheriff es una herramienta que asigna tags a carpetas -- como domain:ticketing y type:data -- y define reglas sobre cuales tags pueden acceder a cuales. Si no cumples, recibes un error de linting: directamente en el IDE y en consola.
// Ejemplo de configuracion de Sheriff
// sheriff.config.ts
export const sheriffConfig = {
tagging: {
'src/app/domains/booking': { domain: 'booking', type: 'feature' },
'src/app/domains/checkin': { domain: 'checkin', type: 'feature' },
'src/app/domains/shared': { domain: 'shared', type: 'ui' },
},
depRules: {
domain: {
booking: ['shared'],
checkin: ['shared'],
},
},
};
El mensaje es concreto: "el modulo domains/checkin/feature-checkin no puede acceder a domains/ticketing/data porque domain:checkin no tiene permiso para domain:ticketing, type:data." Esa precision es clave, porque ese error no solo sirve para developers -- es feedback deterministico para el modelo de lenguaje.
"An architecture that exists only in a presentation does not survive a single sprint." - Source: Manfred Steyer - Angular Architects
tsarch: Mas alla de las capas
Sheriff maneja boundaries entre domains y layers. Pero con Feature Slicing -- donde una feature puede tener sus propios Dumb Components, Stores y servicios de data access juntos en la misma carpeta -- necesitas reglas adicionales.
tsarch es una herramienta inspirada en ArchUnit para TypeScript. Parsea el proyecto via Compiler API y permite verificar dependencias entre archivos con reglas legibles que corren como tests unitarios.
// arch/access-rules.spec.ts
import { filesOfProject } from 'tsarch';
import { describe, expect, it } from 'vitest';
import { anyFileExcept, formatDependency, toDependency } from './utils';
const TS_CONFIG = 'tsconfig.arch.json';
const STORE = String.raw`-store\.ts$`;
const CLIENT = String.raw`-client\.ts$`;
const SMART = String.raw`-(page|search|edit|detail|overview)\.ts$`;
describe('architecture: suffix-based access rules', () => {
it('only stores may access data access (clients)', async () => {
const rule = filesOfProject(TS_CONFIG)
.matchingPattern(anyFileExcept(STORE))
.shouldNot()
.dependOnFiles()
.matchingPattern(CLIENT);
const violations = await rule.check();
expect(violations.map(toDependency).map(formatDependency)).toEqual([]);
});
});
Las reglas se reconocen por sufijos de archivo. Los Smart Components llevan -page, -search, -edit, -detail. Los Stores terminan en -store.ts. Los Clients en -client.ts. Los Coordinators en -coordinator.ts. tsarch verifica que solo los Stores accedan a Clients, que los Stores no se accedan entre si, y que los Dumb Components no accedan a Smart Components.
"Not every architectural constraint can be expressed through layers and Sheriff -- especially with Feature Slicing you need additional guardrails." - Source: Angular Architects - tsarch for AI Coding Agents
Hooks: El feedback loop automatico
Documentar la arquitectura y validarla no basta si el agente no recibe el feedback. Los Stop Hooks resuelven esto: un script que se ejecuta automaticamente cuando el agente termina una ronda, corriendo lint (con Sheriff), los tests de tsarch, y el build. Si algo falla, el agente entra en una nueva ronda y corrige su codigo basandose en el error concreto.
// scripts/hooks/claude-stop-hook.mjs
import process from 'node:process';
import { runChecks } from '../ci-checks.mjs';
const result = runChecks({ capture: true });
if (result.status === 'error') {
process.stderr.write(result.message);
process.exit(2); // Claude Code: block and retry
}
process.exit(0);
Para Cursor el protocolo es diferente: espera un JSON en stdout en vez de un exit code. Pero el core de checks es el mismo -- solo cambia el hook script de unas pocas lineas.
// .claude/settings.json
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "node scripts/hooks/claude-stop-hook.mjs",
"timeout": 600
}
]
}
]
}
}
"A Hook that runs deterministic tools like Sheriff, tests, and build gives the agent a reliable, machine-generated corrective -- and turns an architecture violation into an automatic correction round instead of technical debt." - Source: Angular Architects
Single source of truth para multiples herramientas
Si tu equipo usa Cursor, Claude Code, y tal vez Codex o Antigravity, cada herramienta espera reglas en ubicaciones distintas. La solucion: un AGENTS.md central que todas las herramientas referencian, con un script de sync que copia las configuraciones a los paths que cada una espera.
// Cursor lee de: CLAUDE.md -> @AGENTS.md
// Claude Code lee de: CLAUDE.md -> @AGENTS.md
// Ambos comparten: docs/architecture-boundaries.md
// Skills se copian via: scripts/sync-agent-config.mjs
Hace poco estuve probando este setup en un proyecto y la diferencia es tangible. El agente no solo respeta los boundaries -- cuando detecta que un cambio cruza un domain boundary, pregunta antes de actuar. Eso es exactamente lo que quieres.
"Rules say which project rules must be visible. Skills describe how to work on a specific task. MCP provides access to external tools and knowledge sources." - Source: Angular Architects
Si ya leiste sobre monorepositorios y Angular como fuente de verdad para AI, este enfoque lleva esa idea un paso mas alla: no solo organizas el codigo para que los agents lo entiendan, sino que haces que la arquitectura sea verificable de forma automatica.
El loop completo
El flujo se resume en:
Define la arquitectura: Domains, layers, feature slicing
Documenta las reglas: En docs/architecture-boundaries.md y docs/architecture-state-management.md
Proporciona contexto al agente: Via AGENTS.md, Skills, y Rules
Valida deterministicamente: Con Sheriff (layers) + tsarch (building blocks)
Alimenta errores al agente: Via Stop Hooks
El agente se corrige solo: Hasta que los checks pasen
Referencias
Angular Architects - AI-Assisted Coding: Architecture as an Executable Contract
Angular Architects - Architecture Beyond Layers: tsarch for AI Coding Agents
Keywords: Angular arquitectura AI, tsarch Sheriff Angular, coding agents architecture, Feature Slicing Angular, AI-assisted coding
Meta description: Como usar tsarch y Sheriff para hacer de tu arquitectura Angular un contrato ejecutable que los coding agents respetan automaticamente via Stop Hooks.