Die Code-Produktion moderner Coding-Agents übersteigt heute bei Weitem das Tempo, mit dem wir ihre Ergebnisse lesen oder prüfen können. Für die Entwicklungsgeschwindigkeit ist das ein Gewinn – für die Systemarchitektur allerdings ein erhebliches Risiko. Sprachmodelle streben von Natur aus nach dem Prinzip „es funktioniert“ und nicht nach „es entspricht unserer Zielarchitektur“. Ohne bewusste Gegensteuerung gelangt der hastig erzeugte Datenzugriff direkt in die Komponente, entstehen ungewollte Abhängigkeiten zwischen Domänen, und die mühsam aufgebaute Struktur erodiert mit jedem einzelnen Commit ein Stück weiter.
Die Lösung liegt darin, dass genau diese Schutzmechanismen sich mittlerweile so formulieren lassen, dass der Coding-Agent sie nicht nur zur Kenntnis nimmt, sondern auch befolgt – und dass er bei Regelverstößen eine eindeutige, maschinell erzeugte Rückmeldung erhält, dank derer er seine Arbeit selbstständig korrigieren kann.
Für AI-gestütztes Coding muss Architektur als ausführbarer Vertrag fungieren: niedergelegt in Rules, ausgelöst über aufgabenspezifischen Kontext, kontrolliert durch Sheriff und über Hooks zurückgeführt.
Der vorliegende Beitrag zeigt die praktische Umsetzung dieses Konzepts. Im Mittelpunkt steht die Bereitstellung von Kontext für die Architektur, die Einbindung von Architekturprüfungen wie Sheriff in die Feedback-Schleife der AI sowie eine Konfiguration, die sich über verschiedene Plattformen wie Cursor AI, Claude Code, Codex (GPT) und Googles Antigravity CLI gleichermaßen erstreckt. Einbezogen werden die offiziellen Angular Skills sowie der MCP-Server der Angular CLI, berücksichtigt wird der NgRx Signal Store, ergänzt um eigene Skills. Abschließend fließen ADRs und weitere dokumentierte Entscheidungen in das Gesamtbild ein.
📂 Quellcode (Branch: ai-arc)
Zielarchitektur: Domänen und Layer in Angular
Bevor eine KI in der Lage ist, eine Architektur einzuhalten, muss diese Architektur zunächst festgelegt werden. Unser Beispiel teilt die Anwendung in Domänen auf: Domänen verringern die kognitive Belastung, da die Arbeit an einem Feature jeweils nur einen begrenzten Ausschnitt betrifft, und sie erleichtern die Organisation im Team, weil Zuständigkeiten sauber voneinander abgegrenzt werden können.

Die Abbildung veranschaulicht das zugrunde liegende Prinzip. In den Spalten finden sich die Domänen, in den Zeilen die Layer. Ein Feature beschreibt einen konkreten fachlichen Ablauf, der in der Regel durch Smart Components orchestriert wird. Unter UI fallen wiederverwendbare, „dumme" Bausteine. Der Datenzugriff sowie die Datenmodelle sind Aufgabe von Data, während technische Helferlein in Util residieren. Darüber hinaus existiert eine domänenübergreifende Shared-Schicht für Code, der in mehreren Domänen Verwendung findet.
Was die Zugriffsregeln anbelangt, so sind sie der Kern der Sache. Ein Layer darf lediglich auf die darunterliegenden Layer zugreifen, also in der Reihenfolge feature → ui → data → util. Ein direkter Zugriff zwischen den Domänen ist jedoch untersagt. Stattdessen steht für den Austausch von Code entweder die Shared-Domäne zur Verfügung oder eine definierte API, die kontrollierten Zugriff auf spezifische Bestandteile einer Domäne gewährt.
Auf dieser Basis verfolgen wir das Konzept des Feature Slicing, dessen zentrale Idee die Lokalität ist. Sämtlicher Code, der ausschließlich einem bestimmten Feature dient, wird im entsprechenden Feature-Ordner abgelegt – dies umfasst auch feature-eigene dumb Components, Stores und Hilfsfunktionen. Dadurch bleibt der kognitive Aufwand gering, da zusammengehörige Logik an einem Ort gebündelt ist. Dieser Ansatz hat jedoch auch eine Kehrseite: Sobald lokaler Code an anderer Stelle benötigt wird, ist ein Refactoring erforderlich, das den Code in eine globalere Struktur verschiebt. Später in diesem Artikel wird deutlich, wie sich genau dieser Verschiebeprozess im Rahmen von AI-assisted Coding effektiv automatisieren lässt.

The diagram shows that Feature Slicing is closer to the plain matrix than it might initially appear. It merely adds the option of feature-local UI, data, and utility building blocks, which only move into a deeper shared layer the moment they’re actually used by multiple features.
Enforcing the architecture matrix with Sheriff
An architecture that exists solely in a slide deck won’t survive a single sprint. That’s why we enforce the matrix using Sheriff. Sheriff assigns tags to folders—like domain:ticketing and type:data—and establishes rules about which tags are permitted to access which others. As soon as you step out of line, a linting error appears: instantly in the IDE as you type, and also in the terminal.

Interessanterweise ist die Fehlermeldung sehr präzise: Das Modul domains/checkin/feature-checkin hat keinen Zugriff auf domains/ticketing/data, da für domain:checkin keine Berechtigung für domain:ticketing, type:data besteht. Diese Genauigkeit ist entscheidend, um den nächsten Schritt zu ermöglichen – denn solche Fehler dienen nicht nur als Rückmeldung an das Entwicklungsteam, sondern auch als hochgradig deterministisches Feedback an das Sprachmodell. Anschließend zeigen wir, wie der Coding-Agent diese Meldung aufnimmt und seinen eigenen Code eigenständig repariert.
Eine detaillierte Anleitung zum Einrichten einer derartigen Architektur mit Sheriff und Standalone Components finden Sie im Beitrag Modern Architectures with Angular – Strategic Design with Sheriff.
Kontext schaffen: Rules
Die Qualität eines Coding-Agents hängt direkt vom bereitgestellten Kontext ab. Ohne klare Anweisungen gerät das Modell ins Rätselraten – und es tendiert dabei zumeist zum typischen Stil aus seinen Trainingsdaten, nicht zu unseren Konventionen. Daher ist Kontext kein Bonus, sondern eine Grundbedingung, damit generierter Code überhaupt mit unserer Architektur harmoniert.
Angular selbst übernimmt den Anfang. Das Framework generiert eine AGENTS.md, in der die zentralen Maßgaben für modernes Angular stehen – darunter Signals, Standalone Components, inject(), nativer Control Flow und weiteres. Ein exzellenter Startpunkt, den wir nicht selbst neu entwickeln müssen.
Cursor AI bezieht seine Vorgaben aus dem Verzeichnis .cursor/rules. Um eine doppelte Pflege zu vermeiden, verweist unsere Cursor-spezifische Regel cursor.mdc auf AGENTS.md als verbindliche Referenz:
---
alwaysApply: true
---
Always follow the guidelines defined in the `AGENTS.md` file in the project root.
It is the authoritative source for coding standards, conventions, and best practices.
In case of conflicts, the rules in `AGENTS.md` take precedence.
Das Setzen von alwaysApply: true im Frontmatter bewirkt, dass die Regel bei jeder Anfrage aktiv ist – der Inhalt selbst beschränkt sich auf Verweise zur AGENTS.md. Damit zeichnet sich bereits eine klare Aufgabenverteilung zwischen den Artefakten ab:
- In der
AGENTS.mdstehen ausschließlich die roten Linien sowie kurze Verweise – keine ausgeführten Detailregeln. - Die
docs/architecture-boundaries.mdfungiert als zentrale Referenz für Domänen, Layering, Feature Slicing, Shared Code, öffentliche APIs und Sheriff-Regeln. - Die
docs/architecture-state-management.mdbildet die maßgebliche Quelle für Konventionen zu Store und State Management.
Eine Duplizierung sämtlicher Detailregeln in der AGENTS.md ist also nicht vorgesehen. Zusammengefasst:
Die verbindlichen Architektur- und Coding-Regeln liegen in den Docs. Die AGENTS.md listet die wichtigsten roten Linien und verweist auf die konkreten Regelwerke. Aufgabenspezifischer Kontext wird erst dann herangezogen, wenn die aktuelle Aufgabe ihn erforderlich macht.
Diese Vorgehensweise basiert auf einer bewussten Überlegung zur Kontextgröße: Ein größerer permanenter Prompt-Anteil steigert Kosten, Ablenkung und das Risiko der Kontextverschmutzung. Aus diesem Grund finden nur knappe Always-on-Regeln ihren Platz in der AGENTS.md beziehungsweise in always-on Rules. Die ausführlichen Regeln verbleiben in docs/architecture-boundaries.md und docs/architecture-state-management.md und werden je nach Aufgabenstellung gelesen – aktiviert über Rules, Prompts oder später über Skills. Diesen Aspekt vertiefen wir im Skills-Abschnitt.
Cursor bietet für Rules mehrere Aktivierungsmodi: dauerhaft, über Dateimuster, durch den Agenten anhand der Beschreibung oder per expliziter Referenz. Dadurch nähern sich .mdc-Dateien bereits einer aufgabenspezifischen Kontextsteuerung an: Kurze rote Linien bleiben ständig präsent, detaillierte Regeln werden nur bei passender Aufgabenstellung aktiviert. Konkret verweisen die .mdc-Dateien in .cursor/rules auf docs/architecture-boundaries.md und docs/architecture-state-management.md und unterstützen den Agenten so bei der Einordnung relevanter Aufgaben.
Mit Werkzeugen wie Claude Code lässt sich derselbe Effekt über Prompting erzielen: In lokalen Instruktionsdateien wird dem Agenten mitgeteilt, welche Inhalte wann geladen werden sollen – etwa docs/architecture-boundaries.md bei strukturellen Änderungen und docs/architecture-state-management.md bei Store-Aufgaben. Statt alle Detailregeln permanent mitzuführen, kommt jeweils die zur Aufgabe passende Regel zum Einsatz. Die konkrete Umsetzung mit CLAUDE.md und src/CLAUDE.md wird später betrachtet.
Wer die vollständigen Dateien einsehen möchte, findet sie auf GitHub: architecture.mdc, docs/architecture-boundaries.md sowie docs/architecture-state-management.md.
Allgemeine Architektur-Regeln
Bei den Architekturregeln liegt der Fokus darauf, kritische Punkte eindeutig zu fixieren. Die zentralen Regeln kompakt zusammengefasst:
- Die Sheriff-Konfiguration wird ausschließlich auf ausdrücklichen Wunsch verändert. Insbesondere darf sie nicht abgeschwächt werden, nur um den Linter zufriedenzustellen.
- Feature Slicing: Feature-lokaler Code hat Priorität. Falls er später an anderer Stelle benötigt wird, erfolgt die Verschiebung in einen tieferen Layer.
- Neue Domänen werden nur auf ausdrücklichen Wunsch ergänzt. Das Modell darf jedoch neue Domänen vorschlagen.
- Das Verschieben von Code in den Shared-Bereich erfordert die Zustimmung oder ausdrückliche Anforderung der Benutzerin bzw. des Benutzers. Shared gilt dabei als bewusste Architekturentscheidung – kein Auffangbecken für Importprobleme.
- Entsprechendes gilt für das Veröffentlichen von Code über APIs für andere Domänen. In diesem Fall gestatten zusätzliche Sheriff-Regeln den Zugriff auf eine
api/index.tsmit ausgewählten Exporten in einer Nachbardomäne. - Neuer Code für neue Use Cases orientiert sich am Aufbau vorhandener, bewährter Referenz-Use-Cases (beispielsweise
FlightSearchundFlightEdit). In der Praxis hat sich das als erstaunlich wirkungsvoll erwiesen.
Spezifische Regeln für State Management
Die Vorgaben für den Signal Store sind ebenfalls präzise formuliert. Sie legen unter anderem Folgendes fest:
- Aus dem NgRx Toolkit müssen
withResource,withMutationssowie die Dev-Tools-Integration (withDevtools) verwendet werden. - Für den Datenzugriff ist ein separater Datenzugriffsservice zuständig; der Store selbst kommuniziert nicht direkt mit dem Backend.
- Die Store-Typen sind strikt voneinander getrennt: für Entitäten einer Suchliste kommt
<Entity>SearchStorezum Einsatz, für Entitäten einer Detailansicht<Entity>DetailStore, für Lookup-Entitäten bzw. Vorschlagswerte<Feature>LookupStoreund für UI-State ein eigener Store. - Der Zugriff auf einen anderen Store ist nur nach ausdrücklicher Zustimmung der Benutzerin oder des Benutzers erlaubt.
Namenskonventionen
Nicht alle Anforderungen lassen sich sinnvoll mit Layering und Sheriff umsetzen. Ein solcher Versuch würde die Konfiguration rasch unübersichtlich machen. Deshalb ergänzen wir das Ganze um Namenskonventionen, die leicht zu dokumentieren und vom Modell ohne großen Aufwand zu validieren sind.
Der Store-Zugriff illustriert das gut. Er ist ausschließlich Smart Components vorbehalten, die in unserem Projekt an den Endungen Page, Search, Detail und Edit erkennbar sind – etwa FlightSearch oder FlightEdit.
Auch diese Regeln sind in den Rules verankert. Gerade in modernem Angular, wo der pauschale Suffix Component zunehmend wegfällt, sind semantisch aufgeladene Endungen für Menschen und Maschinen gleichermaßen wertvoll.
Kontext bei Bedarf: Angular Skills und MCP-Server
Würde der gesamte Kontext permanent im Prompt stehen, würde dieser schnell überladen, kostspielig und schwer lesbar. Die Rules laden kontextabhängige Informationen bereits ansatzweise nach – etwa in Cursor über Dateimuster, Beschreibungen oder direkte Verweise, in Claude Code über dedizierte Prompting-Vorgaben. Skills treiben diesen Gedanken weiter, indem sie themenspezifisches Know-how und wiederkehrende Prozesse bündeln.
Ein Skill kapselt also fachspezifisches Wissen und feste Arbeitsabläufe, wobei die Aktivierung je nach Tool automatisch oder manuell erfolgt. Daraus entsteht eine klare Aufgabenteilung:
Während Docs die verbindlichen Architektur- und Coding-Standards definieren, beschreiben Skills das konkrete Vorgehen eines Agents für bestimmte Aufgaben. AGENTS.md listet die zentralen roten Linien auf und verweist auf die zugehörigen Regelwerke.
Skills referenzieren folglich die Docs, ohne sie zu kopieren: docs/architecture-boundaries.md und docs/architecture-state-management.md bleiben die maßgeblichen Detailvorgaben, ergänzt um den passenden Ablauf aus dem jeweiligen Skill.
Offizielle Skills aus der Angular-Community werden kontinuierlich an die aktuelle Framework-Version angepasst. Die Installation erfolgt über die Skills-CLI:
npx skills add https://github.com/angular/skills
Die Skills angular-developer und angular-new-app werden im Verzeichnis .agents/skills/ abgelegt, einer etablierten Konvention, die über verschiedene Werkzeuge hinweg funktioniert. Die Version der installierten Skills wird in einer skills-lock.json dokumentiert, wodurch Updates nachvollziehbar bleiben.
Dieses Vorgehen lässt sich direkt auf eigene Regelwerke übertragen. Für die State-Management-Vorgaben aus docs/architecture-state-management.md wäre ein zusätzlicher, aufgabenbezogener signal-store-Skill sinnvoll. Die docs/architecture-state-management.md bleibt die maßgebliche Referenz für Store-Konventionen; der Skill verweist auf dieses Dokument und definiert den konkreten Ablauf für Routineaufgaben: wann ein neuer Store erforderlich ist, welcher Store-Typ in Frage kommt, wo der Store platziert wird, welche bestehenden Stores als Vorbild dienen und welche Prüfungen nach der Änderung erfolgen müssen. Die Regel bleibt also in der Projektdokumentation verankert; der Skill legt dar, wie der Agent bei Store-bezogenen Aufgaben vorgehen soll.
Dieses Muster lässt sich auch auf Architekturregeln übertragen: Anstatt docs/architecture-boundaries.md ausschließlich über Dateimuster-Rules und Prompting einzubinden, könnte ein eigener Architektur-Skill diese Funktion übernehmen und bei strukturellen Änderungen greifen. Wichtig bleibt die klare Trennung: Der Skill beschreibt den Ablauf und verweist auf die Doku als verbindliche Grundlage – er wird nicht zur Quelle der Wahrheit. Die Regel lebt weiterhin in docs/architecture-boundaries.md, der Skill verweist nur darauf.
Darüber hinaus erweist sich der MCP-Server der Angular CLI als hilfreich. MCP (Model Context Protocol) ist ein offener Standard, der einem Coding-Agenten Zugriff auf externe Werkzeuge und Wissensarchive ermöglicht. Der Angular-MCP-Server liefert dem Agenten unter anderem gut aufbereitete Beispiele und Zugang zur aktuellen Dokumentation – ideal, um Best Practices auch dann einzuhalten, wenn die Trainingsdaten des Modells nicht mehr ganz aktuell sind.
In Cursor AI konfigurieren wir den Server über die Datei .cursor/mcp.json:
{
"mcpServers": {
"angular-cli": {
"command": "npx",
"args": ["-y", "@angular/cli", "mcp"]
}
}
}
Damit ist der Angular-MCP-Server für den Agenten verfügbar, sobald er ihn braucht.
Architekturprüfungen als Hook in die KI-Feedback-Schleife integrieren
An dieser Stelle haben wir dem Modell einiges vermittelt. Doch reines Wissen genügt nicht – wir benötigen ein verlässliches Sicherheitsnetz, das jede Agentenrunde kontrolliert. Genau hierfür sind Hooks gedacht.
Ein Stop-Hook aktiviert sich, wenn der Agent seine Arbeit für abgeschlossen hält, und startet daraufhin ein Node-Skript, das Linter, Tests und Build durchführt. Über den Linter wirkt auch Sheriff mit – unsere Architektur-Matrix wird folglich in jeder Runde automatisiert überprüft.
Wir trennen dabei konsequent die reine Prüfungslogik von der werkzeugspezifischen Anbindung: Ein zentraler Prüfkern erzeugt eine neutrale Ausgabe, während ein kompaktes Hook-Skript pro Werkzeug diese in dessen Format überführt. Dadurch lässt sich das System im kommenden Abschnitt unkompliziert für mehrere Coding-Agents erweitern.
Die eigentliche Prüfungslogik stellt das Herzstück dar und liegt in scripts/ci-checks.mjs. Sie kennt lediglich die Checks selbst – ohne Kenntnis von Exit-Codes oder stdout/stderr:
import { execSync } from 'node:child_process';
const fastSteps = [
'npx ng lint flights',
'npm run test:arch',
'npm run test:scripts',
];
const fullOnlySteps = [
'npx ng test flights --configuration ci',
'npx ng build flights',
];
// Runs the CI steps in order and stops at the first failing one.
// Returns a discriminated result instead of throwing so callers can map it
// to whatever their environment expects (exit code, JSON payload, ...).
export function runChecks({ full = false, capture = false } = {}) {
const steps = full ? [...fastSteps, ...fullOnlySteps] : fastSteps;
for (const step of steps) {
try {
execSync(step, capture ? { encoding: 'utf8' } : { stdio: 'inherit' });
} catch (error) {
const out = capture
? [error.stdout, error.stderr].filter(Boolean).join('\n').trim()
: '';
return {
status: 'error',
message: `Check failed: ${step}\n\n${out || error.message}`,
};
}
}
return { status: 'success' };
}
runChecks processes the steps sequentially, making a deliberate distinction between quick checks (linting with Sheriff included, plus architecture and script tests) and the costlier steps (unit tests and build) that only get added with full. Instead of throwing an exception on failure, the function returns a discriminated result – either { status: 'success' } or { status: 'error', message } containing the collected output. Every caller can then map the outcome to whatever its environment expects, whether that means an exit code, a JSON payload, or something else. Via capture, we determine whether that output gets collected (capture: true) for later feedback to the model or gets passed straight through to the console.
For each coding agent, a dedicated, minimal hook script covers the tool-specific handling: it invokes runChecks and converts the result to match the conventions of its respective tool. Shared across all of them, beyond the core checks, is just a tiny helper function that reads the tool context from stdin (scripts/hooks/read-input.mjs):
import process from 'node:process';
export async function readInput() {
const chunks = [];
for await (const c of process.stdin) {
chunks.push(c);
}
try {
return JSON.parse(Buffer.concat(chunks).toString() || '{}');
} catch {
return {};
}
}
Das Hook-Skript für Cursor liegt unter scripts/hooks/cursor-stop-hook.mjs und sieht folgendermaßen aus:
import process from 'node:process';
import { runChecks } from '../ci-checks.mjs';
import { readInput } from './read-input.mjs';
const input = await readInput();
if (input.status !== 'aborted') {
const result = runChecks({ capture: true });
if (result.status === 'error') {
process.stdout.write(JSON.stringify({ followup_message: result.message }));
process.exit(0);
}
}
process.stdout.write('{}');
process.exit(0);
Cursors Konvention manifestiert sich hier direkt: Bei einem abgebrochenen Durchlauf (status === 'aborted') lässt das Skript die Prüfungen außen vor. Sollte runChecks jedoch ausgeführt werden und einen Fehler entdecken, destilliert sich die Fehlermeldung als followup_message in ein JSON-Objekt auf stdout – Cursor reicht genau dieses Feld an den Agenten als nächste Anweisung weiter. In sämtlichen übrigen Szenarien gibt der Hook ein leeres JSON-Objekt zurück. Der Exit-Code bleibt konsequent auf 0; Cursor orientiert sich an der Ausgabe, um das weitere Vorgehen zu bestimmen.
Die Hook-Registrierung erfolgt in .cursor/hooks.json – wir referenzieren nun das Cursor-Hook-Skript statt direkt das Prüfskript:
{
"version": 1,
"hooks": {
"stop": [
{
"command": "node scripts/hooks/cursor-stop-hook.mjs",
"timeout": 600,
"loop_limit": 3
}
]
}
}
Falls einer der Checks negativ ausfällt – etwa weil Sheriff eine Architekturverletzung anzeigt, ein Test fehlschlägt oder der Build abbricht – startet der Agent eine weitere Iteration. Die Fehlermeldung wird ihm als Eingabe übergeben, und er versucht, das Problem zu beheben. Das loop_limit von drei setzt hierbei eine Obergrenze für die Anzahl der Wiederholungen, um eine Endlosschleife des Agents zu verhindern.
Die abgestufte Reihenfolge der Prüfungen ist im Skript bereits vorgesehen: Zu Beginn werden lediglich die schnellen Checks wie Lint und Sheriff ausgeführt, während die aufwendigeren Schritte – vollständige Tests und Build – erst über full aktiviert werden, etwa bei umfangreicheren Änderungen oder unmittelbar vor einem Merge. Auf diese Weise bleibt das Sicherheitsnetz intakt, ohne dass jede Runde übermäßig kostspielig wird.
Die Wahl eines Node-Skripts ist eine gezielte Entscheidung: Dadurch wird derselbe Check unter macOS, Linux und Windows ohne Plattformabhängigkeiten ausgeführt.
Ein subtiler, jedoch entscheidender Unterschied im Werkzeugverhalten ist festzustellen: Cursor zeigt die Ausführung des Skripts deutlich an. Claude Code hingegen bleibt stumm – hier wird die Ausführung nur dann bemerkt, wenn ein Fehler auftritt.
Modern Angular
Mehr zu Signal Forms und moderner Angular-Architektur findest du in meinem neuen eBook Modern Angular. Es behandelt Signals, Architektur, Testing, KI-Assistenten und praxistaugliche Lösungen für moderne Business-Anwendungen.
Cursor AI und Claude Code aus einer gemeinsamen Definitionsbasis speisen
Es kommt häufig vor, dass Entwicklerteams ihren Mitgliedern die Freiheit bei der Tool-Auswahl lassen. Genau daraus ergibt sich ein handfestes Hindernis: Jede Umgebung verlangt nach Rules, Skills und MCP-Servern an eigenen Pfaden und unter anderen Bezeichnungen. Bei Claude Code etwa heißt die Datei für Rules CLAUDE.md, Skills werden in .claude/skills/ abgelegt und MCP-Server finden ihre Konfiguration in der .mcp.json.
Unser Ziel ist es jedoch, eine zentrale Definitionsbasis zu bewahren, anstatt jede Regel redundant zu pflegen. Der konkrete Ansatz richtet sich nach der Art des Artefakts.
Für die Rules greifen wir auf einen schlanken Mechanismus zurück: Die CLAUDE.md im Projektwurzelordner enthält hauptsächlich einen Zeiger auf die AGENTS.md:
@AGENTS.md
Zusätzlich zeigt eine src/CLAUDE.md auf die Architekturdokumentation. Damit bleibt genau eine verbindliche Referenz bestehen, auf die sämtliche Tools ausgerichtet sind.
Before changing application or library code here, read `docs/architecture-boundaries.md` and apply the architecture rules.
If the change touches state management, also read `docs/architecture-state-management.md` when it exists.
Do not bypass documented domain boundaries. Prefer small, focused changes.
Hier wird der zuvor skizzierte Prompting-Ansatz praktisch umgesetzt: Anstelle von „berücksichtige stets sämtliche Architekturregeln" geben wir die Anweisung kontextabhängig aus. Konkret bedeutet das: Greife bei strukturellen Anpassungen – wie Domains, Layern, Imports, gemeinsam genutztem Code, öffentlichen APIs oder Sheriff-Regeln – auf docs/architecture-boundaries.md zurück. Wenn du Stores anlegst oder veränderst, ziehe docs/architecture-state-management.md heran. Auf diese Weise muss nicht jede einzelne Regel den gesamten Kontext belasten, sondern wird erst bei der entsprechenden Aufgabenstellung geladen.
Die nachfolgende Abbildung veranschaulicht, wie Claude Code und Cursor AI ihren jeweiligen Kontext aufbauen: Beide Tools laden permanent ihre Einstiegsregeln (CLAUDE.md respektive cursor.mdc) und damit auch AGENTS.md; die umfangreichen docs/-Dateien werden hingegen nur bei Bedarf eingebunden.
Beim Umgang mit Skills und MCP-Konfiguration lässt sich der Verweis-Trick nicht anwenden – hier müssen die Dateien physisch an beiden Speicherorten vorhanden sein. Symlinks wären eine naheliegende Option, allerdings unterscheidet sich ihr Verhalten zwischen Windows und Linux/macOS, was in der Praxis häufig zu Problemen führt. Stattdessen setzen wir ein kompaktes Node-Skript ein, das die Dateien dupliziert. Dieses spiegelt .agents/skills/ in .claude/skills/ sowie .cursor/mcp.json in .mcp.json.
Zusätzlich erzeugt das Skript zwei DO_NOT_EDIT-Dateien, um versehentliche Änderungen an den generierten Kopien zu verhindern – eine befindet sich im gespiegelten Skills-Ordner, die andere bei der MCP-Konfiguration. Diese Dateien machen unmissverständlich klar, dass sämtliche Anpassungen an diesen Stellen beim nächsten Sync verloren gehen und in der jeweiligen Quelldatei vorgenommen werden müssen.
Für einen zuverlässigen Ablauf binden wir das Skript in die gängigen Lebenszyklus-Ereignisse ein: Es läuft über das prepare-Skript von npm (also nach jedem npm install) und wird zusätzlich durch einen Pre-Commit-Hook ausgelöst, sobald eine der Quellen verändert wurde. Auf diese Weise bleiben die Kopien stets synchron, ganz ohne manuelles Eingreifen.
Damit kommen wir zum Stop-Hook, den wir ebenfalls aus einer zentralen Quelle speisen. Mit dem zweiten Werkzeug wird deutlich, warum wir die Indirektion eingebaut haben – denn jeder Coding-Agent definiert eigene Regeln für die Kommunikation mit einem Stop-Hook. Zwei Aspekte spielen dabei eine zentrale Rolle. Zum einen der Exit-Code, also der Rückgabewert, mit dem ein Prozess sein Resultat an das aufrufende Programm weitergibt (bei Systemprozessen spricht man von Exit-Code oder Exit-Status, nicht von „Result Code"). Bei Claude Code signalisiert ein Exit-Code von 2 „blockieren und erneut versuchen", während Cursor auch bei Fehlern den Code 0 liefert und stattdessen anhand der Ausgabe über das weitere Vorgehen entscheidet. Zum anderen die Verwendung von stdout und stderr: Cursor liest ein JSON-Objekt aus stdout und erwartet die Antwort für das Modell in einem spezifischen Feld; Claude Code hingegen nutzt den Text aus stderr als Agenten-Feedback. Einige Werkzeuge reichen über stdin zusätzlichen Kontext an den Hook weiter, etwa ob der Durchlauf abgebrochen wurde.
Genau auf diese Unterschiede ist das jeweilige Hook-Skript ausgelegt: Der Prüfkern in ci-checks.mjs (sowie das reduzierte read-input.mjs) bleibt unangetastet – für Claude Code kommt lediglich ein weiteres, ebenso kompaktes Hook-Skript (scripts/hooks/claude-stop-hook.mjs) hinzu:
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);
}
process.exit(0);
Claude's convention is what comes into play here: rather than emitting a JSON object on stdout, Claude Code relies on the exit code as its indicator — 2 signifies "halt execution and pass back the text written to stderr as feedback for the agent". The handful of lines merely translate the outcome into Claude's expectations; the entire validation core remains common.
The hook isn't registered with Claude Code through a dedicated hook file but instead within the .claude/settings.json:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "node scripts/hooks/claude-stop-hook.mjs",
"timeout": 600
}
]
}
]
}
}
Cursor AI und Claude Code nutzen also denselben Validierungskern – die Unterschiede beschränken sich auf ein paar Zeilen im jeweiligen Hook-Skript.
Erweiterung um GPT, Codex CLI, Google Antigravity und andere
Inzwischen folgen zahlreiche gängige Coding-Agents ähnlichen Mustern: Eine AGENTS.md enthält projektweite Anweisungen, Skills liegen häufig in einem separaten Skills-Ordner, und MCP-Server werden über tool-eigene Konfigurationsdateien eingebunden. Doch die Feinheiten variieren von Werkzeug zu Werkzeug – etwa bei Hooks, lokalen Instruktionsdateien oder der MCP-Einrichtung. So verlangt Codex die MCP-Konfiguration in einer TOML-Datei (beispielsweise .codex/config.toml), während Antigravity auf ein JSON-Format wie mcp_config.json setzt.
Aus diesem Grund betrachten wir AGENTS.md, .agents/skills/ und unsere MCP-Basiskonfiguration als die maßgeblichen Quellen. Ein kompaktes Sync-Skript generiert daraus die jeweils benötigten Zielformate. Soll ein weiteres Tool angebunden werden, genügt meist eine Erweiterung dieses Skripts. Weil das Skript bewusst einfach aufgebaut ist, eignet sich genau diese Anpassung besonders für AI-gestütztes Coding: Der Agent kann die Doku des neuen Werkzeugs recherchieren, das erforderliche Zielformat ableiten und das Sync-Skript entsprechend ergänzen.
Für ebendiese Aufgabe existieren bereits fertige Lösungen wie rulesync oder ruler. Sie verwalten Rules, Skills, MCP-Server – teilweise auch Hooks – aus einer zentralen Konfiguration und verteilen sie an die verschiedenen Coding-Agents. Wir haben uns hier dennoch absichtlich für die schlanke Eigenentwicklung entschieden: Dadurch bleiben die De-facto-Standards AGENTS.md und .agents/skills/ unsere direkte Quelle der Wahrheit, statt uns an das jeweilige Quellformat eines solchen Tools zu koppeln (etwa ein zentrales .rulesync/-Verzeichnis). Wer ohnehin eine Vielzahl von Agents und Artefakttypen synchronisieren muss, für den können diese Tools sinnvoller sein – für unser überschaubares Setup überwiegt jedoch der Vorteil, direkt auf den etablierten Standard-Dateien zu basieren.
Das gleiche Konzept lässt sich auf die Stop-Hooks übertragen. Da die eigentliche Prüflogik in ci-checks.mjs (und der kleinen read-input.mjs) werkzeugunabhängig ist, bedeutet ein weiteres Tool meist nur ein neues Hook-Skript mit wenigen Zeilen, das dessen Konventionen für Exit-Code und stdout/stderr erfüllt – plus dessen Registrierung an der vorgesehenen Stelle. Auch diese Aufgabe eignet sich ideal für AI-gestütztes Coding: Der Agent schlägt die Hook-Dokumentation des neuen Werkzeugs nach und leitet das passende Hook-Skript daraus ab.
Genau darin liegen die Stärken von Sprachmodellen: Sie glänzen dabei, unser bestehendes Setup an die gegenwärtigen Konventionen weiterer Coding-Agents anzupassen. Man kann den Agenten einfach bitten, die Doku des neuen Tools heranzuziehen, das passende Zielformat zu ermitteln und das Sync-Skript entsprechend zu modifizieren – die Integration eines weiteren Werkzeugs wird damit selbst zu einer Aufgabe für AI-gestütztes Coding.
AI-gestütztes Coding in der Praxis: ein neues Feature generieren
Genug Theorie – betrachten wir das Setup im Einsatz. Wir fordern im Coding Agent an, dass wir zukünftig auch Airports verwalten möchten. Der Einfachheit halber werden diese im Speicher gehalten, in einem Datenzugriffsservice namens AirportClient. Zusätzlich sollen Airports beim Warten von Flügen über Dropdown-Felder auswählbar sein.

Das Resultat überzeugt. Für die Features airports und booking entsteht jeweils ein eigener Store – die richtige Entscheidung, da ihr Zustand getrennt bleibt. Ein Filter in einem Bereich darf den anderen nicht beeinflussen. (Sollte das doch gewünscht sein, müssten wir die architecture-boundaries.md anpassen.) Beide Stores greifen jedoch auf denselben AirportClient im Data-Layer zurück.
Falls der Agent eine Linting-Regel wie die Sheriff-basierte Architekturvorgabe verletzt – oder Tests beziehungsweise Build fehlschlagen –, startet die IDE dank des Stop-Hooks automatisch eine weitere Iteration, bis alle Prüfungen erfolgreich sind.
Interessant wird es bei variierenden Anforderungen. Wenn wir explizit verlangen, dass beide Features denselben Store teilen, zieht der Coding-Agent diesen von sich aus in den Data-Layer um – genau wie in der architecture-boundaries.md festgehalten. Damit bewältigt er nebenbei eine entscheidende Hürde des Feature Slicings: das Refactoring, also das Verschieben von lokalem Code in globale Bereiche, sobald dies tatsächlich erforderlich ist.

Bleiben wir in derselben Domäne, darf der Agent den Code gemäß den dokumentierten Regeln umbauen – zum Beispiel das besagte Verschieben des Stores in den Data-Layer. Deutlich spannender wird es, wenn wir den AirportClient zusätzlich in der checkin-Domäne einsetzen möchten. Damit stoßen wir an Domänengrenzen, und genau hier muss der Agent eine Rückfrage stellen. Der Agent konfrontiert uns nun mit einer Frage – und das ist beabsichtigt. In der architecture-boundaries.md haben wir festgeschrieben, dass in genau dieser Situation Rücksprache erforderlich ist, weil ein Zugriff über Domänengrenzen hinweg eine bewusste Architekturentscheidung darstellt. Als Optionen bieten sich beispielsweise an: den Code in shared auslagern, ihn über eine öffentliche API der ticketing-Domäne bereitstellen oder die Anforderung anders zu formulieren.

Wenn wir den AirportClient in den Shared-Bereich verlagern möchten, ergibt sich das oben dargestellte Bild: Beide Fachbereiche nutzen jetzt den gemeinsamen Code, der in shared liegt.

Setzen wir hingegen auf den API-Ansatz, verbleibt der AirportClient innerhalb der ticketing-Domäne und wird über eine api/index.ts-Datei nach außen bereitgestellt. Der Agent modifiziert die Sheriff-Konfiguration entsprechend, sodass domain:checkin auf domain:ticketing/api zugreifen kann – jedoch ausschließlich auf die dort explizit exportierten und autorisierten Komponenten:

Rules und Skills in einem Kreislauf verbessern
Es gehört zur ehrlichen Selbsteinschätzung: Gute Rules und Skills entstehen nicht in einem einzigen Durchgang, sondern über einen andauernden Zyklus. Handelt der Agent wider Erwarten, sind diese Dateien nachzubessern und zu präzisieren. Sprachmodelle und Coding-Agents sind genau für diesen Zweck übrigens sehr nützlich – man kann sie direkt auffordern, aus einem unerwünschten Handeln eine exaktere Vorgabe zu erzeugen.
Aus meiner Erfahrung hängt es zudem sehr vom jeweiligen Modell ab, wie detailliert und klar die Anweisungen ausfallen müssen. Einige Modelle genügen sich mit kurzen Hinweisen, während andere sehr eindeutige, fast schon akribische Beschreibungen benötigen. Es zahlt sich aus, die Regeln an das konkrete Modell anzupassen, das im Einsatz ist.
Hinterlegte Prompts und der Plan-Modus
Zwei handfeste Tipps aus der Praxis können über Frust oder flüssiges Arbeiten entscheiden. Zunächst hat sich der Einsatz von hinterlegten Prompts bei mir bewährt, die ich bei Bedarf in den Chat einfüge. Das unterstützt das wiederholte Feintuning und besonders ähnliche Folgeaufgaben enorm.
Weiterhin lasse ich den Agenten häufig zuerst ausdrücklich einen Plan aufstellen – durch den Plan-Modus, den mehrere IDEs heutzutage bereitstellen – und gebe den Entwurf erst danach zur Umsetzung frei. Auf diese Weise sind Missverständnisse ausgeräumt, noch bevor eine einzige Zeile Code entsteht.
Eigene Skills: Architektur-Review in Eigenregie
Ausgefeilte Prompts lassen sich außerdem zu eigenen Skills ausbauen. Hier ist man nicht auf die Erstellung von Code begrenzt – es eignen sich auch wiederkehrende Prozesse wie ein Architektur-Review. Der dafür vorgesehene eigene Skill befindet sich unter .agents/skills/architecture-review/SKILL.md und ist absichtlich knapp gehalten:
---
name: architecture-review
description: Review Angular code against the repository architecture rules in docs/architecture-boundaries.md, including Sheriff boundaries, layering, feature slicing, shared code, and state management conventions.
---
# Review Angular Architecture
Use this skill when reviewing Angular code for architectural quality.
Before reviewing, read:
- `docs/architecture-boundaries.md`
- `AGENTS.md` if present
- `docs/architecture-state-management.md` if state management is involved
- the relevant Sheriff configuration
- the changed files and their imports
Treat `docs/architecture-boundaries.md` as the source of truth.
...
Der Skill erhebt die docs/architecture-boundaries.md zur verbindlichen Referenz, etabliert einen strukturierten Überprüfungsablauf und legt das angestrebte Ergebnisformat fest – von der Kurzfassung über nach Priorität sortierte Befunde bis hin zu konkreten Lösungen. Auf diese Weise verwandelt sich ein vages „Wirf kurz einen Blick darauf" in einen wiederholbaren Prüfprozess.
Das Review beginnt mit einem unkomplizierten Prompt:
Perform an architecture review for the new airport-related features (skill architecture-review).
Es ist nicht erforderlich, den Namen des Skills explizit anzugeben. Falls jedoch absolute Sicherheit gewünscht ist, dass der Coding-Agent genau diesen Skill verwendet, lässt er sich direkt anführen.
Erweiterter Kontext: ADRs und MCP-Server für Confluence und Ähnliches
Architekturentscheidungen besitzen fast immer eine Vorgeschichte: Warum relaxed Layering? Warum Feature-Slicing? Diese Begründungen dokumentieren Teams in Architecture Decision Records (ADRs). Im Branch ai-arc-adr integrieren wir derartige ADRs – unter anderem zu relaxed Layering, Domänen, Feature-Slicing und State Management.
Hierbei gilt es, den Kontext des Agenten nicht übermäßig zu belasten. Falls sämtliche ADRs permanent mitgeführt würden, hätte das negative Auswirkungen auf die Übersicht und das Tokenbudget. Daher generieren wir aus den ADRs kompakte Richtlinien – auch dies lässt sich mit Sprachmodellen effektiv umsetzen – und setzen von diesen Richtlinien Verweise auf die vollständigen ADRs, damit der Coding-Agent bei Bedarf darauf zugreifen kann. In der Datei docs/architecture-boundaries.md präsentiert sich das ungefähr folgendermaßen:
## Shared Code
_(derived from [ADR-0004](adr/0004-feature-slicing-and-shared-code.md))_
- Promote code to a shared area only when at least two independent features require it.
- Avoid premature shared abstractions.
## State Management
_(derived from [ADR-0003](adr/0003-ngrx-signal-store-for-state.md))_
- Follow `docs/architecture-state-management.md` where applicable.
Die Regel steht also platzsparend im Kontext, die dazugehörige Erklärung bleibt nur einen Klick entfernt. So bleibt der Prompt schlank, und die Entscheidungen sind dennoch jederzeit nachvollziehbar.
Abgesehen von ADRs im Repository lohnt sich auch ein Blick auf die Tooling-Landschaft: Dienste wie Confluence stellen mittlerweile ebenso MCP-Server bereit. Dadurch kann der Agent auf Wunsch direkt auf dort dokumentierte Entscheidungen und Vorgaben zugreifen – ganz ohne dass wir diese ins Repository übertragen müssen.
Fazit
Bei AI-assisted Coding entstehen robuste Architekturen nie von allein – sie sind das Resultat gezielt vorbereiteten Kontexts. Dabei ist eine klare Rollentrennung sinnvoll:
Rules definieren, welche Projektregeln sichtbar sein müssen. Skills legen fest, wie bei einer spezifischen Aufgabe vorgegangen wird. MCP öffnet den Zugang zu externen Werkzeugen und Wissensquellen.
Zudem müssen wir uns auf unterschiedliche Dateikonventionen der verschiedenen Agenten einstellen. Damit dennoch eine zentrale Quelle der Wahrheit erhalten bleibt, setzen wir auf Verweise und kleine Skripte zum Kopieren der Dateien.
Mindestens genauso zentral ist die Rückkopplungsschleife: Ein Hook, der deterministische Werkzeuge wie Sheriff, Tests und Build ausführt, liefert dem Agenten eine zuverlässige, maschinelle Kontrollinstanz – und macht aus einem Architekturverstoß eine automatisierte Korrekturrunde statt angesammelter technischer Schuld.
In unserem Fall hat sich eine vertikale Architektur mit Domänen und Feature Slicing bewährt. Auch die bekannte Schwierigkeit von Feature Slicing – das spätere Verschieben lokalen Codes in globalere Kontexte – ließ sich elegant bewältigen, Weil der Agent dieses Refactoring anhand klarer Regeln selbstständig ausführt und bei kritischen Entscheidungen Rücksprache hält.
Und nicht zuletzt: Es bleibt ein kontinuierlicher, iterativer Prozess. Rules und Skills entwickeln sich mit den Erfahrungen weiter, die man mit dem jeweiligen Modell sammelt. Wer bereit ist, diese Leitplanken stetig zu verfeinern, profitiert von beiden Welten zugleich – der Schnelligkeit der AI und der Stabilität einer gut durchdachten Architektur.
Mehr dazu: Angular Architecture Workshop: AI & Signals (Remote, Interaktiv, Advanced)
Unser Workshop wurde komplett überarbeitet und legt nun einen besonderen Schwerpunkt auf AI-assisted Architecture und Signals. Werde zum Experten für unternehmensweite, langlebige Angular-Anwendungen und lerne, KI gezielt für wartbare Architekturen einzusetzen.
Deutsche Version | English Version
FAQ
Wieso genügt eine einmalige Architektur-Erklärung gegenüber dem Coding-Agent nicht?
Ein Sprachmodell strebt standardmäßig nach lauffähigem Code, nicht nach der Befolgung Ihrer Zielarchitektur. Fehlen explizite, permanent verfügbare Vorgaben (Rules) sowie eine deterministische Rückkopplungsschleife (Hook mit Sheriff, Tests, Build), schwächt sich die Struktur mit der Zeit ab.
Auf welchem Weg erreicht das Architektur-Feedback das Modell?
Durch einen Stop-Hook führt die IDE ein Node-Skript aus, das den Linter (inklusive Sheriff), Tests und Build anstößt. Gibt es Fehlschläge, startet der Agent automatisch eine weitere Iteration und verbessert seinen Code anhand der präzisen Fehlermeldung.
Wie lassen sich Cursor AI und Claude Code parallel bedienen?
Rules werden über Verweise gebündelt (etwa wenn CLAUDE.md auf AGENTS.md zeigt), wodurch eine einzige Referenzquelle entsteht. Skills und die MCP-Konfiguration überträgt ein Node-Skript an die jeweils verlangten Ablageorte – bewusst nicht über Symlinks, weil deren Verhalten je nach Betriebssystem divergiert.
Worin unterscheiden sich Rules, Skills und MCP-Server?
Rules legen fest, welche Projektregeln sichtbar sein müssen – kompakte rote Linien permanent, ausführliche Regeln nur, wenn die Aufgabe dazu passt. Skills vereinen aufgabenspezifisches Wissen mit konkreten Handlungsabläufen und legen dar, wie ein Agent bei bestimmten Aufträgen vorgeht; je nach Werkzeug werden sie automatisch oder manuell aktiviert. MCP-Server eröffnen dem Agenten Zugriff auf externe Werkzeuge und Wissensquellen, zum Beispiel die Angular-Dokumentation oder dokumentierte Entscheidungen in Confluence.
Wie integrieren sich ADRs, ohne den Kontext zu überlasten?
Aus den ADRs werden kurze, kontextbezogene Regeln abgeleitet; auf die ausführlichen ADRs wird lediglich referenziert. Dadurch bleibt der Prompt kompakt, und der Agent kann die Begründung bei Bedarf abrufen.


