{"id":34037,"date":"2026-06-08T12:05:28","date_gmt":"2026-06-08T10:05:28","guid":{"rendered":"https:\/\/www.angulararchitects.io\/?p=34037"},"modified":"2026-06-08T21:43:15","modified_gmt":"2026-06-08T19:43:15","slug":"architecture-beyond-layers-tsarch-for-ai-agents","status":"publish","type":"post","link":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/","title":{"rendered":"Architecture Beyond Layers: tsarch for AI Coding Agents"},"content":{"rendered":"<div class=\"wp-post-series-box series-ai-assisted-angular-architecture wp-post-series-box--expandable\">\n\t\t\t<input id=\"collapsible-series-ai-assisted-angular-architecture6a272761796ba\" class=\"wp-post-series-box__toggle_checkbox\" type=\"checkbox\">\n\t\n\t<label\n\t\tclass=\"wp-post-series-box__label\"\n\t\t\t\t\tfor=\"collapsible-series-ai-assisted-angular-architecture6a272761796ba\"\n\t\t\ttabindex=\"0\"\n\t\t\t\t>\n\t\t<p class=\"wp-post-series-box__name wp-post-series-name\">\n\t\t\tThis is post 2 of 2 in the series <em>&ldquo;AI-assisted Angular Architecture&rdquo;<\/em>\t\t<\/p>\n\t\t\t<\/label>\n\n\t\t\t<div class=\"wp-post-series-box__posts\">\n\t\t\t<ol>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/www.angulararchitects.io\/en\/blog\/reliable-angular-architectures-with-ai-assisted-coding\/\">AI-Assisted Coding: Architecture as an Executable Contract<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><span class=\"wp-post-series-box__current\">Architecture Beyond Layers: tsarch for AI Coding Agents<\/span><\/li>\n\t\t\t\t\t\t\t<\/ol>\n\t\t<\/div>\n\t<\/div>\n<p>In the first part of this series we built the big guardrail: architecture rules get documented, brought into the coding agent's context through Rules and Skills, checked with Sheriff, and fed back as deterministic feedback via Stop hooks.<\/p>\n<p>That closes the feedback loop for domain and layer boundaries. With Feature Slicing, however, additional rules arise <em>within<\/em> a feature \u2013 for example between Smart and Dumb Components, Stores, and services for data access.<\/p>\n<p>This is exactly where this second part comes in. We extend the setup with <strong>tsarch<\/strong>, a tool for TypeScript projects inspired by the well-known <strong>ArchUnit<\/strong> library. With it, naming and access conventions become executable architecture rules \u2013 checked as a unit test and wired into the same agent feedback loop that already carried Sheriff in the first part.<\/p>\n<p><em>Where layers reach their limits, naming conventions take over: tsarch checks them deterministically and turns an architecture violation into machine feedback that the coding agent picks up.<\/em><\/p>\n<p>\ud83d\udcc2 <a href=\"https:\/\/github.com\/angular-architects\/flights42\/tree\/ai-arc\">Source Code<\/a> (Branch: <code>ai-arc<\/code>)<\/p>\n<h2>Architecture Rules via Building Blocks in Addition to Layers<\/h2>\n<p>In the first part we used Sheriff to channel the communication between modules: domains represent functional boundaries, layers technical ones. However, not all technical constraints can be expressed well through layers \u2013 especially not when, with Feature Slicing, we allow a feature to have its own Dumb Components, data access services, and Stores. These building blocks then sit together in the feature folder rather than separated into their own layers.<\/p>\n<p>That is why we additionally constrain things by <strong>Building Blocks<\/strong> here. What matters is no longer just which layer a building block sits in, but what <em>kind<\/em> of building block it is \u2013 and which building blocks may access which:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/building-blocks.svg\" alt=\"Building blocks and their permitted accesses: A Smart Component accesses a Store, the Store accesses the Data Access client. Alternatively, the Smart Component goes through a Coordinator that combines several Stores.\" \/><\/p>\n<p>The central rule for our example project is: a <strong>Smart Component<\/strong> accesses a <strong>Store<\/strong>, and only the Store accesses the <strong>Data Access client<\/strong>. Smart Components therefore must not access the client directly, and Stores must not access other Stores.<\/p>\n<p>Especially when using lightweight Stores, you often need a <strong>Coordinator<\/strong> that stores no data itself but, for a use case, provides the states from different Stores \u2013 possibly from different layers \u2013 and ties them together via <code>computed<\/code> signals. This use-case orchestration is handled by the Coordinator, which from the component's point of view looks like a &quot;real&quot; Store. This way we avoid Store-to-Store dependencies, and thus cycles, without giving up the convenience of a bundled view across multiple Stores.<\/p>\n<p>There is one deliberate exception for <strong>Dumb Components<\/strong>: they only get access to Stores in the same folder or in child folders. In this case we assume local state management that is a pure implementation detail of the Dumb Component. Apart from that, Dumb Components must not access any of the other building blocks described here.<\/p>\n<h2>Recognizing Building Blocks via File Suffixes<\/h2>\n<p>We recognize the individual building blocks by their file suffixes. This fits well with the current conventions of the Angular team, implemented by the CLI: components, services, and directives no longer get default suffixes. Not because suffixes are bad, but because generic suffixes like <code>Component<\/code> or <code>Service<\/code> offer too little value. The Angular team explicitly emphasized, however, that you can assign your own, semantically stronger suffixes \u2013 and that is exactly what we use here.<\/p>\n<p>In our demo project I decided on the following suffixes:<\/p>\n<ul>\n<li><strong>Smart Components<\/strong> carry a descriptive use-case suffix: <code>-page<\/code>, <code>-search<\/code>, <code>-edit<\/code>, <code>-detail<\/code>, or <code>-overview<\/code> \u2013 for example <code>flight-search.ts<\/code> with the class <code>FlightSearch<\/code> or <code>luggage-overview.ts<\/code> with <code>LuggageOverview<\/code>.<\/li>\n<li><strong>Stores<\/strong> end in <code>-store.ts<\/code> \u2013 for example <code>flight-search-store.ts<\/code> with <code>FlightSearchStore<\/code> or <code>passenger-detail-store.ts<\/code> with <code>PassengerDetailStore<\/code>.<\/li>\n<li><strong>Coordinators<\/strong> of Stores end in <code>-coordinator.ts<\/code> \u2013 for example <code>summary-coordinator.ts<\/code> with <code>SummaryCoordinator<\/code>.<\/li>\n<li><strong>Data access<\/strong> (clients) ends in <code>-client.ts<\/code> \u2013 for example <code>flight-client.ts<\/code> with <code>FlightClient<\/code> or <code>airport-client.ts<\/code> with <code>AirportClient<\/code>.<\/li>\n<li><strong>Dumb Components<\/strong> are recognized by the suffixes <code>-card<\/code> and <code>-pane<\/code>, or by the fact that they live in a <code>ui<\/code> folder \u2013 for example <code>flight-card.ts<\/code>.<\/li>\n<\/ul>\n<p>The authoritative, executable definition of these conventions lives in the tsarch unit test itself, which we will look at more closely in a moment. There the suffixes appear as regular expressions and are thus unambiguous \u2013 both for humans and for coding agents.<\/p>\n<h2>Setting Up tsarch<\/h2>\n<p>tsarch parses the TypeScript project via the Compiler API and lets you check dependencies between files in readable, fluently phrased rules. We install the npm package as a dev dependency:<\/p>\n<pre><code class=\"language-bash\">npm install -D tsarch<\/code><\/pre>\n<p>For tsarch to know which files belong to the project, it needs a <code>tsconfig<\/code>. Since tsarch reads it directly and does not resolve <code>extends<\/code>, we <em>unfortunately<\/em> have to provide a small dedicated <code>tsconfig.arch.json<\/code> that contains exactly what tsarch needs: the relevant compiler options (such as <code>target<\/code>, <code>module<\/code>, <code>moduleResolution<\/code>, and <code>experimentalDecorators<\/code> for Angular's decorators) as well as literally specified <code>include<\/code> and <code>exclude<\/code> paths:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;compilerOptions&quot;: {\n    &quot;target&quot;: &quot;ES2022&quot;,\n    &quot;module&quot;: &quot;esnext&quot;,\n    &quot;moduleResolution&quot;: &quot;bundler&quot;,\n    &quot;experimentalDecorators&quot;: true,\n  },\n  &quot;include&quot;: [&quot;src\/**\/*.ts&quot;],\n  &quot;exclude&quot;: [&quot;src\/**\/*.spec.ts&quot;, &quot;src\/app\/testing\/**&quot;, &quot;node_modules&quot;]\n}<\/code><\/pre>\n<p>This duplication of the <code>tsconfig<\/code> is admittedly not pretty \u2013 I hope that a future PR to tsarch will add support for resolving <code>extends<\/code>. Until then, the small dedicated file is a manageable price.<\/p>\n<p>The architecture rules themselves are perfectly ordinary tests. They do not run in the browser-based <code>ng test<\/code> environment, though, but with Vitest in a Node environment, because tsarch analyzes the TypeScript project via the compiler and the file system. The configuration for this is straightforward:<\/p>\n<pre><code class=\"language-ts\">import { defineConfig } from &#039;vitest\/config&#039;;\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: &#039;node&#039;,\n    include: [&#039;arch\/**\/*.spec.ts&#039;],\n    testTimeout: 60_000,\n    hookTimeout: 60_000,\n  },\n});<\/code><\/pre>\n<p>Finally, we add a script to <code>package.json<\/code> that starts these architecture tests:<\/p>\n<pre><code class=\"language-json\">&quot;test:arch&quot;: &quot;vitest run --config vitest.arch.config.ts&quot;<\/code><\/pre>\n<p>With that, the scaffolding is in place. The actual rules live in <code>arch\/access-rules.spec.ts<\/code> and rely on a few helper functions from <code>arch\/utils.ts<\/code>. We will look at both, rule by rule, in the following sections.<\/p>\n<h2>Restricting Access to Data Access<\/h2>\n<p>In our architecture, only Stores \u2013 and the <code>ai<\/code> layer \u2013 may access data access services or clients. All other building blocks, in particular Smart and Dumb Components, must take the detour through a Store. We deliberately exempt the <code>ai<\/code> layer because, in concert with an agent, it handles the orchestration of the application at runtime.<\/p>\n<p>The following snippet shows the head of the spec file with the suffix constants and the first rule. The further <code>it<\/code> blocks are replaced by an ellipsis and follow in the next sections:<\/p>\n<pre><code class=\"language-ts\">import { filesOfProject } from &#039;tsarch&#039;;\nimport { describe, expect, it } from &#039;vitest&#039;;\n\nimport {\n  anyFileExcept,\n  formatDependency,\n  isLocalAccess,\n  toDependency,\n} from &#039;.\/utils&#039;;\n\nconst TS_CONFIG = &#039;tsconfig.arch.json&#039;;\n\nconst STORE = String.raw`-store\\.ts$`;\nconst CLIENT = String.raw`-client\\.ts$`;\nconst SMART = String.raw`-(page|search|edit|detail|overview)\\.ts$`;\nconst DUMB = String.raw`(-(card|pane)\\.ts$|\/ui(-[^\/]+)?\/)`;\nconst AI_LAYER = String.raw`\/ai\/`;\nconst COORDINATOR = String.raw`-coordinator\\.ts$`;\n\ndescribe(&#039;architecture: suffix-based access rules&#039;, () =&gt; {\n  it(&#039;only stores may access data access (clients)&#039;, async () =&gt; {\n    const rule = filesOfProject(TS_CONFIG)\n      .matchingPattern(anyFileExcept(STORE, AI_LAYER))\n      .shouldNot()\n      .dependOnFiles()\n      .matchingPattern(CLIENT);\n\n    const violations = await rule.check();\n    expect(violations.map(toDependency).map(formatDependency)).toEqual([]);\n  });\n\n  \/\/ \u2026\n});<\/code><\/pre>\n<p>We define the suffixes as regular expressions, using <code>String.raw<\/code> for them. This template tag returns the string without interpreting escape sequences like <code>\\.<\/code> \u2013 so a <code>\\<\/code> stays a real backslash and does not turn into a line break or similar. This saves us the double escaping (<code>\\\\.<\/code> instead of <code>\\.<\/code>) and keeps the regular expressions easy to read.<\/p>\n<p>The rule reads almost like a sentence: take all files of the project that are <strong>neither a Store nor part of the <code>ai<\/code> layer<\/strong> (<code>anyFileExcept(STORE, AI_LAYER)<\/code>), and make sure they do <strong>not<\/strong> depend on files ending in <code>-client.ts<\/code>. <code>rule.check()<\/code> returns a list of the violations; if there are none, the expected list is empty and the test is green. If the rule triggers, thanks to <code>formatDependency<\/code> we see directly which file illegally accesses which client.<\/p>\n<p>The helper functions from <code>utils.ts<\/code> used in the first <code>it<\/code> make the rule readable. <code>anyFileExcept<\/code> builds a pattern from the passed suffixes that selects exactly those files that are <strong>none<\/strong> of the named kinds. Technically, each kind becomes a negative lookahead (<code>(?!...)<\/code>, meaning &quot;must not occur&quot;):<\/p>\n<pre><code class=\"language-ts\">export function anyFileExcept(...kinds: string[]): string {\n  return String.raw`^${kinds.map((kind) =&gt; `(?!.*${kind})`).join(&#039;&#039;)}.*\\.ts$`;\n}<\/code><\/pre>\n<p><code>anyFileExcept(STORE, AI_LAYER)<\/code> thus produces a pattern that matches every <code>.ts<\/code> file that is neither a Store nor located in the <code>ai<\/code> layer. This is how we express &quot;everything except \u2026&quot; declaratively, without enumerating every permitted case one by one.<\/p>\n<p>A concrete example makes this tangible: <code>anyFileExcept(STORE, AI_LAYER)<\/code> puts a negative lookahead in front of the actual pattern for each passed positive pattern and thus produces<\/p>\n<pre><code class=\"language-text\">^(?!.*-store\\.ts$)(?!.*\/ai\/).*\\.ts$<\/code><\/pre>\n<p>Read aloud, this means: match every path that ends in <code>.ts<\/code>, <strong>provided<\/strong> it contains neither <code>-store.ts<\/code> at the end nor <code>\/ai\/<\/code> anywhere. A file like <code>flight-search.ts<\/code> therefore matches, while <code>flight-search-store.ts<\/code> and everything under <code>\/ai\/<\/code> drop out.<\/p>\n<p><code>toDependency<\/code> and <code>formatDependency<\/code> prepare the matches for nicely readable output. <code>toDependency<\/code> extracts the source and target file from a tsarch violation, and <code>formatDependency<\/code> formats them as <code>source -&gt; target<\/code>:<\/p>\n<pre><code class=\"language-ts\">export function toDependency(violation: unknown): Dependency {\n  const { dependency } = violation as {\n    dependency: { sourceLabel: string; targetLabel: string };\n  };\n  return { source: dependency.sourceLabel, target: dependency.targetLabel };\n}\n\nexport function formatDependency(dependency: Dependency): string {\n  return `${dependency.source} -&gt; ${dependency.target}`;\n}<\/code><\/pre>\n<p>This precise output is decisive later on: it is not only feedback for developers, but also a deterministic signal with which the coding agent can fix a violation in a targeted way.<\/p>\n<p><div style=\"\nmargin: 8px 0;\npadding: 22px;\nborder: 1px solid #e5e7eb;\nborder-radius: 14px;\nbackground: #f8fafc;\n\"><\/p>\n<h3 style=\"margin-top:0\">Modern Angular<\/h3>\n<p>You can find more about Signal Forms and modern Angular architecture in my new eBook Modern Angular. It covers Signals, architecture, testing, AI assistants, and practical solutions for modern business applications.<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/modern-book\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/01\/cover-klein.png\" width=\"400\" alt=\"Modern Angular - Signal-first, Architecture-first, Practice-first\" style=\"cursor:pointer !important\"><\/a><\/p>\n<p><a style=\"cursor:pointer !important\" href=\"https:\/\/www.angulararchitects.io\/modern-book\">More about the book \u2192<\/a>\n<\/div>\n<\/p>\n<h2>Restricting Access to Stores<\/h2>\n<p>Only Smart Components \u2013 and again the <code>ai<\/code> layer \u2013 get access to Stores. There is, however, the exception already mentioned: Dumb Components may access Stores in the same folder or in child folders. In this case we assume local state management that is an implementation detail of the Dumb Component. We also exempt Coordinators, since their very job is to combine several Stores.<\/p>\n<p>The corresponding <code>it<\/code> implements this mix of general rule and local exception:<\/p>\n<pre><code class=\"language-ts\">it(&#039;only smart components may access a store (locality and ai excepted)&#039;, async () =&gt; {\n  \/\/ Coordinators are a dedicated service layer that may combine several stores.\n  const rule = filesOfProject(TS_CONFIG)\n    .matchingPattern(anyFileExcept(SMART, AI_LAYER, STORE, COORDINATOR))\n    .shouldNot()\n    .dependOnFiles()\n    .matchingPattern(STORE);\n\n  \/\/ Exception: when the store is co-located (same or child folder)\n  const violations = (await rule.check())\n    .map(toDependency)\n    .filter(({ source, target }) =&gt; !isLocalAccess(source, target));\n\n  expect(violations.map(formatDependency)).toEqual([]);\n});<\/code><\/pre>\n<p>The base rule selects all files except Smart Components, the <code>ai<\/code> layer, Stores, and Coordinators, and forbids them access to Stores. The local exception cannot be expressed through a pattern alone \u2013 it depends on the relative position of two files. That is why we filter the found violations afterwards and let through all those where the Store is local to the accessing file.<\/p>\n<p>This check is handled by the helper function <code>isLocalAccess<\/code>. It returns <code>true<\/code> when the target file sits in the same folder as the source or in a child folder of it:<\/p>\n<pre><code class=\"language-ts\">export function isLocalAccess(source: string, target: string): boolean {\n  const sourceFolder = posix.dirname(source);\n  const targetFolder = posix.dirname(target);\n  return (\n    targetFolder === sourceFolder || targetFolder.startsWith(`${sourceFolder}\/`)\n  );\n}<\/code><\/pre>\n<p>This way, feature-local state management remains a permitted implementation detail, while cross-domain or cross-feature access to foreign Stores stays reserved for Smart Components.<\/p>\n<h2>Stores Must Not Access Each Other<\/h2>\n<p>This is possibly a somewhat more controversial rule, but in my setup I decided not to allow Store-to-Store access \u2013 above all to avoid cycles. If a use case needs access to multiple Stores, it can use the Coordinator described above or rely on eventing between the Stores. The latter leads to looser coupling, the former is simpler.<\/p>\n<p>The third <code>it<\/code> formulates this rule directly and without exceptions:<\/p>\n<pre><code class=\"language-ts\">it(&#039;stores must not access other stores&#039;, async () =&gt; {\n  \/\/ Combining several stores is the job of a coordinator, not of a store.\n  const rule = filesOfProject(TS_CONFIG)\n    .matchingPattern(STORE)\n    .shouldNot()\n    .dependOnFiles()\n    .matchingPattern(STORE);\n\n  const violations = await rule.check();\n  expect(violations.map(toDependency).map(formatDependency)).toEqual([]);\n});<\/code><\/pre>\n<p>Every file ending in <code>-store.ts<\/code> must not depend on any other <code>-store.ts<\/code> file. If a Store does need state from another, that is a clear signal to introduce a Coordinator \u2013 and that is exactly where this rule steers us.<\/p>\n<h2>Dumb Components Must Not Access Smart Components<\/h2>\n<p>Dumb Components are reusable, presentation-oriented building blocks. They should know nothing about the use cases in which they are used \u2013 and certainly not access Smart Components, which orchestrate those use cases. If they did, their reusability would be gone and cycles would quickly arise.<\/p>\n<p>The fourth <code>it<\/code> implements this requirement:<\/p>\n<pre><code class=\"language-ts\">it(&#039;dumb components must not access smart components&#039;, async () =&gt; {\n  const rule = filesOfProject(TS_CONFIG)\n    .matchingPattern(DUMB)\n    .shouldNot()\n    .dependOnFiles()\n    .matchingPattern(SMART);\n\n  const violations = await rule.check();\n  expect(violations.map(toDependency).map(formatDependency)).toEqual([]);\n});<\/code><\/pre>\n<p>The <code>DUMB<\/code> pattern matches both the suffixes <code>-card<\/code> and <code>-pane<\/code> and everything that lives in a <code>ui<\/code> folder; the <code>SMART<\/code> pattern matches the use-case suffixes <code>-page<\/code>, <code>-search<\/code>, <code>-edit<\/code>, <code>-detail<\/code>, and <code>-overview<\/code>. The rule thus keeps the direction of flow clean: Smart Components may use Dumb Components, but not the other way around.<\/p>\n<h2>Connecting tsarch with a Coding Agent<\/h2>\n<p>Knowledge alone is not enough \u2013 just like Sheriff in the first part, we wire tsarch into a <strong>Stop hook<\/strong> as a deterministic safety net. To do so, we simply add the architecture tests to the shared quality checks that the hook runs on every round. These checks live in the script <code>scripts\/ci-checks.mjs<\/code>, already known from the first part, which separates the fast steps from the more expensive ones. tsarch joins the fast checks, right next to Lint (including Sheriff):<\/p>\n<pre><code class=\"language-js\">[...]\n\nconst fastSteps = [\n  &#039;npx ng lint flights&#039;,\n  &#039;npm run test:arch&#039;\n];\n\n[...]<\/code><\/pre>\n<p>With that, <code>npm run test:arch<\/code> \u2013 and with it all the tsarch rules \u2013 runs as a fixed part of the same loop that already carries Lint (including Sheriff). The more expensive steps such as browser tests and the build remain reserved for the full runs, while the Stop hook deliberately executes only the fast checks. If an architecture rule triggers, the agent goes into a new round and receives the concrete <code>source -&gt; target<\/code> message as input, with which it corrects itself.<\/p>\n<p>Registering the hook for Claude Code is done as usual via <code>.claude\/settings.json<\/code>:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;hooks&quot;: {\n    &quot;Stop&quot;: [\n      {\n        &quot;matcher&quot;: &quot;&quot;,\n        &quot;hooks&quot;: [\n          {\n            &quot;type&quot;: &quot;command&quot;,\n            &quot;command&quot;: &quot;node scripts\/hooks\/claude-stop-hook.mjs&quot;,\n            &quot;timeout&quot;: 600\n          }\n        ]\n      }\n    ]\n  }\n}<\/code><\/pre>\n<p>How to provide the same setup for Cursor AI, Codex, Google's Antigravity, and other coding agents \u2013 including a shared source of truth and small sync scripts \u2013 is described in detail in the first part of this series. The tsarch rules slot seamlessly into this model, because they are invoked through the same <code>ci-checks<\/code>.<\/p>\n<h2>Keeping Rules and Architecture Documents in Sync<\/h2>\n<p>As valuable as the Stop hook is \u2013 the best violation is the one that never arises in the first place. That is why the same rules belong not only in the tsarch test but also in the <strong>architecture documents<\/strong> picked up by the Rules of the respective coding agents. In <code>docs\/architecture-boundaries.md<\/code> we accordingly recorded that only Stores (and the <code>ai<\/code> layer) may access data access services directly, and in <code>docs\/architecture-state-management.md<\/code> that Stores do not depend on each other, but that a Coordinator handles the combining.<\/p>\n<p>If these requirements are cleanly documented and marked as architecture-relevant in the Rules, the agent reads them already <strong>before<\/strong> a change and proactively avoids the violation. Ideally, the Stop hook is then not needed at all \u2013 it acts as a last safety net should the agent overlook a requirement after all.<\/p>\n<h2>Test Drive: Stop Hook Against a Risky Rename Prompt<\/h2>\n<p>How well this interplay works is shown by a deliberately tricky prompt:<\/p>\n<pre><code>Rename the SummaryCoordinator to SummaryStore.<\/code><\/pre>\n<p>This rename would turn a Coordinator, by name, into a Store \u2013 and thus violate our rules, because a &quot;Store&quot; with <code>-store.ts<\/code> would suddenly no longer be allowed to combine several Stores. When the constraints were formulated accordingly in the docs and the Rules pointed out that the docs contain architecture-relevant information about suffixes, the tested coding agents decided to read the docs and informed the user about a possible upcoming architecture violation:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/before-rename.png\" alt=\"The coding agent reads the architecture documents before the rename, recognizes the looming convention violation, and points it out to the user before changing anything.\" \/><\/p>\n<p>If, on the other hand, we toned down these Rules and information \u2013 in some cases it was even enough not to mention the importance of the suffixes in the Rules, so that the agent did not perceive the architecture docs as relevant for a simple rename at all \u2013 it complied with the request and renamed the Coordinator. The architecture violation was then caught by the Stop hook, however: tsarch triggered, and the coding agent thereupon informed the user and offered to undo the action:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/after-rename.png\" alt=\"After the rename, the tsarch test triggers in the Stop hook; the coding agent reports the detected architecture violation and offers to revert the change.\" \/><\/p>\n<p>Both paths therefore lead to the goal \u2013 just at different points. If the docs and Rules are sharpened, the protection takes effect proactively; if a gap remains, the Stop hook catches the violation deterministically.<\/p>\n<h2>Addition: Verify-and-Fix Skill<\/h2>\n<p>The Stop hook runs automatically after every round of the agent and, for performance reasons, limits itself to the fast checks. For a deliberately triggered, full check \u2013 for example before a commit or push \u2013 a dedicated <strong>Skill<\/strong> is a good complement. In the linked project it lives under <a href=\"https:\/\/github.com\/angular-architects\/flights42\/blob\/ai-arc\/.agents\/skills\/verify-and-fix\/SKILL.md\"><code>.agents\/skills\/verify-and-fix\/SKILL.md<\/code><\/a>:<\/p>\n<pre><code class=\"language-markdown\">---\nname: verify-and-fix\ndescription: [...]\n---\n\n# Full Verify and Fix\n\nRun the full quality checks and resolve every problem until they pass. The stop\nhook only runs the fast checks, so this skill is the on-demand full pass before\ncommitting or pushing.\n\n## Run\n\nnpm run verify\n\nIt stops at the first failing step.\n\n[...]<\/code><\/pre>\n<p>The Skill starts the full suite via <code>npm run verify<\/code> \u2013 including the tsarch architecture tests \u2013 and works through it in a propose-and-confirm loop: on a failure, the agent investigates the cause, proposes a concrete fix, and waits for the user's explicit confirmation before changing anything. Lint, Sheriff, and the architecture rules must never be softened just to make a check go green \u2013 only the code gets fixed.<\/p>\n<h2>Test Drive: verify-and-fix Against a Manual Architecture Violation<\/h2>\n<p>We also look at this Skill in a concrete case. This time we rename the Coordinator <strong>manually<\/strong> into a Store \u2013 exactly the architecture violation we just want to avoid \u2013 and then start the Skill via a simple prompt:<\/p>\n<pre><code>\/verify-and-fix<\/code><\/pre>\n<p>The Skill runs the full checks, tsarch reports the violation, and the agent presents its findings together with the cause and a proposed solution:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/proposed.png\" alt=\"With the propose-and-confirm policy enabled, the Skill changes nothing on its own but presents the detected violation and a concrete fix proposal to the user for confirmation.\" \/><\/p>\n<h2>Trade-offs and Limits<\/h2>\n<p>As useful as convention-based rules are \u2013 they have limits you should be aware of:<\/p>\n<ul>\n<li><strong>Suffixes are not truth, but a convention.<\/strong> They describe the intent of a building block but do not enforce it.<\/li>\n<li><strong>Wrong naming produces wrong classification.<\/strong> Anyone who names a file incorrectly \u2013 deliberately or by accident \u2013 removes it from the matching rule or subjects it to the wrong one.<\/li>\n<li><strong>Barrel files can obscure rules<\/strong> when dependencies only run through bundled re-exports and are not cleanly resolved down to the actual source.<\/li>\n<\/ul>\n<p>So there are ways to circumvent the whole thing. tsarch should be understood as one of several lines of defense \u2013 alongside Sheriff, the architecture documents, and the human review \u2013 and not as carte blanche to blindly check in generated code. The value lies in the fact that the most common and most mechanical violations are caught deterministically, not in preventing every conceivable workaround.<\/p>\n<h2>More on This: Angular Architecture Workshop: AI &amp; Signals (Remote, Interactive, Advanced)<\/h2>\n<p>The interplay of AI and architecture \u2013 that is, exactly what this series is about \u2013 is one of the major topics of our workshop. We have completely revamped it and now put a special focus on AI-assisted Architecture and Signals. Become an expert in enterprise-wide and long-lived Angular applications and learn to use AI for maintainable architectures instead of softening them over time.<\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/training\/advanced-angular-architecture-workshop\/\"><img decoding=\"async\" src=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/workshop-en.png\" alt=\"Angular Architecture Workshop\" style=\"width:100%; display: block; margin-left: auto; margin-right:auto\" \/><\/a><\/p>\n<p><a href=\"https:\/\/www.angulararchitects.io\/en\/training\/advanced-angular-architecture-workshop\/\">German Version<\/a> | <a href=\"https:\/\/www.angulararchitects.io\/en\/training\/advanced-angular-architecture-workshop\/\">English Version<\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>Not all technical constraints can be solved through layers \u2013 especially not with Feature Slicing, where building blocks like Stores, Clients, and Dumb Components sit together feature-locally. tsarch closes this gap by tying architecture rules to naming conventions, checking them as a unit test, and feeding them into the AI's feedback loop via the same Stop hook as Sheriff.<\/p>\n<p>Since the same rules are also part of the architecture documents, the protection ideally takes effect proactively and the Stop hook remains the last safety net. Anyone who prefers a deliberately triggered full check can alternatively activate the same checks through a Skill \u2013 either fixing automatically or in propose-and-confirm mode.<\/p>\n<h2>FAQ<\/h2>\n<p><strong>What is tsarch?<\/strong><br \/>\ntsarch takes the idea from the well-known ArchUnit library and transfers it to TypeScript\/JavaScript projects. It parses the project via the Compiler API and checks dependencies between files in readable, fluently phrased rules that run as unit tests.<\/p>\n<p><strong>When is Sheriff not enough and you need tsarch?<\/strong><br \/>\nSheriff channels the communication between modules, e.g. along domains and layers. As soon as Feature Slicing allows a feature to bring its own Dumb Components, Stores, and Clients, that is no longer enough. tsarch then ties the rules to Building Blocks, which we make recognizable via file suffixes such as <code>-store.ts<\/code>, <code>-client.ts<\/code>, or <code>-coordinator.ts<\/code>.<\/p>\n<p><strong>How do you wire tsarch into the feedback loop of a coding agent?<\/strong><br \/>\nThe rules run as a perfectly ordinary Vitest test and are wired in via a Stop hook \u2013 a script that the coding agent runs automatically at the end of every round. If a rule triggers, the agent gets the concrete error message as deterministic feedback and corrects itself.<\/p>\n<p><strong>How do the Stop hook and the verify-and-fix Skill differ?<\/strong><br \/>\nThe Stop hook runs automatically after every round and, for performance reasons, limits itself to the fast checks. The verify-and-fix Skill is triggered by the user on demand via a prompt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is post 2 of 2 in the series &ldquo;AI-assisted Angular Architecture&rdquo; AI-Assisted Coding: Architecture as an Executable Contract Architecture Beyond Layers: tsarch for AI Coding Agents In the first part of this series we built the big guardrail: architecture rules get documented, brought into the coding agent&#8217;s context through Rules and Skills, checked with [&hellip;]<\/p>\n","protected":false},"author":25,"featured_media":34040,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_price":"","_stock":"","_tribe_ticket_header":"","_tribe_default_ticket_provider":"","_ticket_start_date":"","_ticket_end_date":"","_tribe_ticket_show_description":"","_tribe_ticket_show_not_going":false,"_tribe_ticket_use_global_stock":"","_tribe_ticket_global_stock_level":"","_global_stock_mode":"","_global_stock_cap":"","_tribe_rsvp_for_event":"","_tribe_ticket_going_count":"","_tribe_ticket_not_going_count":"","_tribe_tickets_list":"[]","_tribe_ticket_has_attendee_info_fields":false,"footnotes":""},"categories":[18],"tags":[],"class_list":["post-34037","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","post_series-ai-assisted-angular-architecture"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects<\/title>\n<meta name=\"description\" content=\"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects\" \/>\n<meta property=\"og:description\" content=\"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\" \/>\n<meta property=\"og:site_name\" content=\"ANGULARarchitects\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-08T10:05:28+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-08T19:43:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/sujet2-en.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"628\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Manfred Steyer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/sujet2-en.jpg\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Manfred Steyer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\"},\"author\":{\"name\":\"Manfred Steyer\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\"},\"headline\":\"Architecture Beyond Layers: tsarch for AI Coding Agents\",\"datePublished\":\"2026-06-08T10:05:28+00:00\",\"dateModified\":\"2026-06-08T19:43:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\"},\"wordCount\":2992,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\",\"name\":\"Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects\",\"isPartOf\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png\",\"datePublished\":\"2026-06-08T10:05:28+00:00\",\"dateModified\":\"2026-06-08T19:43:15+00:00\",\"description\":\"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png\",\"width\":1672,\"height\":941},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.angulararchitects.io\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Architecture Beyond Layers: tsarch for AI Coding Agents\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#website\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"name\":\"ANGULARarchitects\",\"description\":\"AngularArchitects.io\",\"publisher\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#organization\",\"name\":\"ANGULARarchitects\",\"alternateName\":\"SOFTWAREarchitects\",\"url\":\"https:\/\/www.angulararchitects.io\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"contentUrl\":\"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg\",\"width\":644,\"height\":216,\"caption\":\"ANGULARarchitects\"},\"image\":{\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/github.com\/angular-architects\",\"https:\/\/www.linkedin.com\/company\/angular-architects\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a\",\"name\":\"Manfred Steyer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g\",\"caption\":\"Manfred Steyer\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects","description":"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/","og_locale":"en_US","og_type":"article","og_title":"Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects","og_description":"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.","og_url":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/","og_site_name":"ANGULARarchitects","article_published_time":"2026-06-08T10:05:28+00:00","article_modified_time":"2026-06-08T19:43:15+00:00","og_image":[{"width":1200,"height":628,"url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/sujet2-en.jpg","type":"image\/jpeg"}],"author":"Manfred Steyer","twitter_card":"summary_large_image","twitter_image":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/sujet2-en.jpg","twitter_misc":{"Written by":"Manfred Steyer","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#article","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/"},"author":{"name":"Manfred Steyer","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a"},"headline":"Architecture Beyond Layers: tsarch for AI Coding Agents","datePublished":"2026-06-08T10:05:28+00:00","dateModified":"2026-06-08T19:43:15+00:00","mainEntityOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/"},"wordCount":2992,"commentCount":0,"publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png","inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/","url":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/","name":"Architecture Beyond Layers: tsarch for AI Coding Agents - ANGULARarchitects","isPartOf":{"@id":"https:\/\/www.angulararchitects.io\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage"},"thumbnailUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png","datePublished":"2026-06-08T10:05:28+00:00","dateModified":"2026-06-08T19:43:15+00:00","description":"Not every architectural constraint can be expressed through layers and Sheriff \u2013 especially with Feature Slicing you need additional guardrails. This second part shows how to enforce convention-based architecture rules with tsarch, an ArchUnit-inspired tool for TypeScript projects, and how to wire them into the feedback loop of coding agents like Claude Code, Cursor AI, and others via a Stop hook.","breadcrumb":{"@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#primaryimage","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2026\/06\/bg-2.png","width":1672,"height":941},{"@type":"BreadcrumbList","@id":"https:\/\/www.angulararchitects.io\/en\/blog\/architecture-beyond-layers-tsarch-for-ai-agents\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.angulararchitects.io\/en\/"},{"@type":"ListItem","position":2,"name":"Architecture Beyond Layers: tsarch for AI Coding Agents"}]},{"@type":"WebSite","@id":"https:\/\/www.angulararchitects.io\/en\/#website","url":"https:\/\/www.angulararchitects.io\/en\/","name":"ANGULARarchitects","description":"AngularArchitects.io","publisher":{"@id":"https:\/\/www.angulararchitects.io\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.angulararchitects.io\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.angulararchitects.io\/en\/#organization","name":"ANGULARarchitects","alternateName":"SOFTWAREarchitects","url":"https:\/\/www.angulararchitects.io\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/","url":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","contentUrl":"https:\/\/www.angulararchitects.io\/wp-content\/uploads\/2023\/07\/AA-Logo-RGB-horizontal-inside-knowledge-black.svg","width":644,"height":216,"caption":"ANGULARarchitects"},"image":{"@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/github.com\/angular-architects","https:\/\/www.linkedin.com\/company\/angular-architects\/"]},{"@type":"Person","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/f3de69c1e2bdb5ba04d8d2f5f998b81a","name":"Manfred Steyer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.angulararchitects.io\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8778dfb353992fa3a0d909beee085a088891e5bfce65cdb3631801da527cf11b?s=96&d=mm&r=g","caption":"Manfred Steyer"}}]}},"_links":{"self":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/34037","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/comments?post=34037"}],"version-history":[{"count":3,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/34037\/revisions"}],"predecessor-version":[{"id":34091,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/posts\/34037\/revisions\/34091"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media\/34040"}],"wp:attachment":[{"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/media?parent=34037"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/categories?post=34037"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.angulararchitects.io\/en\/wp-json\/wp\/v2\/tags?post=34037"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}