Zwei Codebases, kein gemeinsamer Code, keine gemeinsame Domäne, kein
gemeinsamer Tech-Stack. Die eine ist eine Next.js-14-Pages-Router-App,
die Millionen von Listings ausliefert, Zustand-State, SCSS-Module,
Optimizely-A/B-Tests, i18n via Phrase. Die andere ist ein
multi-tenant Institutions-ERP mit 294 View-Komponenten, 239
Datenbanktabellen, einem Hono-Backend mit 74 Route-Dateien, vier
Sprachen inklusive Bengali und Gujarati und einem 14-Rollen-RBAC-Modell,
in dem jede Query eine Tenant-id tragen muss, sonst shippt sie nicht.
Ich habe für beide einen Claude-Code-Layer gebaut. Gleiche Kernidee: ein Set aus Skills, Commands, Hooks und Memory-Dateien, das Claude Code von einem fähigen Assistenten in etwas verwandelt, das eher einem zweiten Engineer gleicht, der die Regeln der Codebase bereits kennt. Als ich an beiden Projekten parallel arbeitete, hatte ich 37 Commands im Marktplatzprodukt, 47 Skills im ERP und 14 Hooks in jedem. Das hier ist, was ich beim Aussortieren gelernt habe, welche Teile mitreisen und welche zu Hause bleiben mussten.
Was ohne Änderung mitgereist ist
Die Session-Boundary-Hooks zogen wörtlich um. Ein
SessionStart-Hook, der git diff --name-only main ausführt und die
geänderten Dateien ausgibt, kostet dreißig Zeilen Shell. Ich schrieb
ihn einmal für das Marktplatzprodukt und warf ihn am selben Nachmittag
ins ERP. Genauso der Stop-Hook, der tsc --noEmit laufen lässt,
wenn die Session endet und der Build kaputt ist, will ich das wissen,
bevor ich das Terminal schließe. Keiner der beiden Hooks weiß, worum
es im Projekt geht. Sie erzwingen einfach eine Disziplin, die überall
gilt.
Das Memory-Struktur-Pattern übertrug sich komplett. Beide Repos
haben jetzt ein memories/repo/-Verzeichnis mit vier Dateien:
modules.md (was jeder Teil der Codebase besitzt), decisions.md
(Architekturentscheidungen und warum sie getroffen wurden),
conventions.md (Patterns, entdeckt durch Lesen, nirgendwo
dokumentiert) und known-flakes.md (ein Register von Testfehlern,
die Rauschen sind, kein Signal). Claude liest sie zu Beginn relevanter
Sessions. Die Struktur ist identisch; die Inhalte sind es
offensichtlich nicht.
Die Linting-Post-Hooks (Prettier, ESLint, Stylelint) zogen ohne jede Änderung um. Eine Datei nach einem Edit zu formatieren ist mechanisch und projekt-agnostisch. Der Punkt ist: die Hooks auf der untersten Ebene, die am nächsten am Dateisystem, sind mit weitem Abstand die portabelsten.
Was an der Naht einen Rewrite brauchte
Der implement-Command im Marktplatzprodukt ist ein
Neun-Phasen-Orchestrator: Ticket lesen, betroffenen Bereich auditen,
planen, scaffolden, implementieren, Self-Review gegen die
Projektregeln, fixen, typechecken, zusammenfassen. Er entstand aus dem
Zusehen, wie Claude bei Schritt drei Abkürzungen nahm und dann eine
Stunde damit verbrachte, die Folgen eines übersprungenen Audits zu
entwirren. Der Command erzwingt das Audit. Er funktioniert gut, wenn
die Regeln an einen Ort passen, die CLAUDE.md des Marktplatzprodukts
hat 187 Zeilen und deckt alles ab.
Die Oberfläche des ERP ist ungefähr sechsmal größer. Eine einzige
CLAUDE.md für 239 Tabellen und 58 Architekturregeln zu pflegen war
nach Monat zwei nicht mehr haltbar. Ich wechselte auf 58 separate
Regel-Dateien in .claude/rules/, jede für ein Anliegen
(sql-safety.md, fee-ledger.md, mutation-wiring.md,
state-machines.md und so weiter), mit @-Imports, die nur die für
die aktuelle Aufgabe relevanten Regeln hereinziehen. Das
implement-Konzept überlebte; die Dateistruktur, die es trägt, musste
von Grund auf neu gebaut werden.
Derselbe Split passierte bei den Hooks. Im Marktplatzprodukt drehen
sich die Post-Tool-Use-Hooks um Code-Style: ESLint, Prettier,
Stylelint, dann tsc. Im ERP laufen die zuerst, und danach laufen
acht Domain-Validator-Hooks nach jedem File-Write:
check-ddl-safety.sh (keine unsicheren Migrations-Patterns),
validate-service-pattern.sh (jede Service-Datei muss sowohl den
lokalen PGlite-Adapter als auch den Remote-Hono-Adapter
implementieren), check-seed-version-sync.sh (SEED_VERSION muss nach
Schema-Änderungen synchron bleiben), detect-dead-buttons.sh (kein
UI-Button ohne verdrahteten Handler) und vier weitere. So etwas
schreibt man nicht für ein Marktplatzprodukt. Die Failure-Modes des
ERP sind andere, ein falsch konfigurierter Service-Adapter fällt in
Produktion still auf lokale Daten zurück; ein toter Button in einem
Gebührenzahlungs-Formular ist ein Support-Ticket.
Was ich gelöscht und als etwas Besseres neu gebaut habe
Das Marktplatzprodukt hat drei A/B-Test-Commands:
ab-test-kickoff.md, ab-test-health.md, ab-test-wrapup.md. Sie
kennen die IDs des Experiment-Frameworks, die interne
Flag-Naming-Konvention und das Cleanup-Pattern, wenn ein Experiment
endet. Auf dieser Codebase sind sie wirklich nützlich. Überall sonst
sind sie völlig bedeutungslos.
Ich machte den Fehler, sie generalisieren zu wollen. Ich verbrachte einen Nachmittag damit, einen "generischen Experiment-Workflow"-Skill zu schreiben, der das A/B-Framework als Parameter akzeptierte. Niemand benutzte ihn, weil er keine Meinungen hatte, und Skills ohne Meinungen sind nicht nützlich, sie werden nur zu einer wortreicheren Art, einen Prompt zu schreiben. Die richtige Lektion war: die Experiment-Skills im Marktplatzprodukt lassen, akzeptieren, dass das äquivalente Problem im ERP (Feature-Rollouts hinter RBAC-Gates) sein eigenes dediziertes Tooling braucht, und aufhören, ein Ding beide Jobs machen zu lassen.
Der scan-stores.md-Command des Marktplatzprodukts ist eine ähnliche
Geschichte. Er auditiert Zustand-Store-Definitionen auf gängige
Probleme: stale Selectors, fehlende shallow-Equality-Checks, State,
der in eine URL gehört. Es ist ein guter Command. Das ERP nutzt
TanStack Query für Server-State und Zustand nur für ephemeren
UI-State. Ein Wholesale-Port wäre nutzlos gewesen. Ich schrieb einen
deutlich schmaleren refactor-state.md-Skill, der die Patterns
abdeckt, die dort tatsächlich vorkommen, und zog weiter.
Die eine strukturelle Entscheidung, die alles verändert hat
Die Skills des Marktplatzprodukts sind Skills. Die wichtigsten
Workflows des ERP sind Subagents: bulk-module-scaffold.md,
port-to-backend.md, extract-queries.md. Der Unterschied zählt.
Ein Skill wird von einem Menschen aufgerufen, läuft einen
abgeschlossenen Workflow und stoppt. Ein Subagent wird von einem
anderen Agenten gespawnt, hat einen definierten Scope, nutzt ein
eingeschränktes Tool-Set und meldet zurück. port-to-backend.md
erstellt eine Hono-Route, verdrahtet den Middleware-Stack,
aktualisiert die Service-Datei mit einem Dual-Mode-Adapter und liefert
eine Zusammenfassung. Er läuft innerhalb einer größeren
Orchestrierung, die in einer Session fünf Service-Dateien portieren
kann. Das mit einem Skill und einem Menschen in der Schleife zwischen
jedem Schritt zu versuchen skaliert nicht.
Das Marktplatzprodukt braucht keine Subagents, weil seine Aufgaben sich nicht so zusammensetzen. Aber in einem Projekt mit 74 Backend-Route-Dateien und einem strikten Adapter-Pattern, dem jede einzelne folgen muss, ist die Fähigkeit, eine wohldefinierte Teilaufgabe an einen Agenten mit begrenzter Tool-Allowlist zu delegieren, der Unterschied zwischen einer Migration, die man in zwei Stunden orchestriert, und einer, die man zwei Tage lang babysittet.
Der Teil, den ich jedes Mal falsch mache
Ich schreibe Eval-Sets immer noch nicht früh genug. Keines der beiden
Projekte hat ein __evals__/-Verzeichnis. Die Hooks fangen
Regressionen auf Datei-Ebene; nichts fängt das Modell, das nach einem
Versions-Update über die Zeit im Reasoning eines Skills driftet. Ich
weiß, dass das die Lücke ist. Ich habe schon darüber geschrieben. Ich
fixe es immer noch nicht schnell genug.
Der Rest des Toolings verdient sein Geld. Die Session-Boundary-Hooks, die Memory-Dateien, die Domain-Validator-Hooks im ERP, die haben alle die Zeit zurückgezahlt, die es gekostet hat, sie zu schreiben. Was gescheitert ist, war entweder zu generisch, um nützlich zu sein, oder zu spezifisch für eine Codebase, um den Umzug zu überleben. Der Skill, der reist, ist der mit starken Meinungen, die für das Problem vor ihm zufällig richtig sind. Das ist keine Regel über AI-Tooling. Das ist einfach eine Regel.