Skip to content

Code analysis

The Cratis Stack

The build enforces the conventions

Cratis is convention-heavy by design — model-bound commands, [EventType] events, declarative projections, generated proxies, vertical slices, and React views that stay thin. Conventions make the easy path the right one, but only if something catches you when you stray. Cratis does that with Roslyn analyzers in dotnet build and ESLint rules in the JavaScript build, so the feedback lands where a developer or AI agent can act on it.

A convention you can break silently isn’t a convention — it’s a suggestion. If you append a type that isn’t marked [EventType], or write a reactor method with the wrong signature, the framework can’t discover it, and the failure shows up at runtime as “nothing happened.” The analyzers move that failure left: the build fails, the message tells you exactly which rule and how to fix it, and several rules ship a one-click code fix.

The same pattern matters on the frontend. Generated proxy files should not produce lint noise, view models should not call React hooks, and Components consumers should not import the root barrel or bypass the Cratis dialog wrappers. Those are architectural choices, so Cratis packages them as ESLint rules instead of leaving them as tribal knowledge.

This matters doubly in the AI-native workflow. When an agent scaffolds a slice, these gates are the same guardrail for the agent that they are for you — a wrong shape fails, the agent reads the diagnostic, and it corrects itself. The coding rules in the AI configuration teach the conventions; the build gates enforce them.

Each analyzer ships inside the package it validates — there’s nothing extra to install. Reference the package and the rules are active on your next build.

The Chronicle analyzer enforces the event-sourcing shapes — events, projections, reducers, reactors, and constraints. A representative slice:

RuleCatches
CHR0001A type appended to an event sequence that’s missing [EventType] (quick fix adds it)
CHR0004 / CHR0006A reactor / reducer method whose signature doesn’t match an allowed shape
CHR0015 / CHR0017A projection or constraint with side effects (injecting ICommandPipeline / IEventLog)
CHR0016 / CHR0018Imperative code inside a Define() that should only contain builder calls
CHR0021An event type that isn’t a record (immutability)

The error-level [EventType] rules (CHR0001, CHR0002, CHR0003, CHR0005, CHR0007) come with an automatic code fix. See the full CHR catalog for all 21 rules.

The Arc.Core analyzer validates the model-bound CQRS shapes — the ones the writing-correct-examples discipline keeps reiterating, now checked by the compiler:

RuleCatches
ARC0001A [ReadModel] query method that doesn’t return the read model (or an allowed wrapper)
ARC0002A command-like type missing its [Command] attribute
ARC0003A Handle() method declared somewhere other than the command type itself
ARC0004A [Command] type with no public Handle() method

And for Arc on Chronicle, ARCCHR0001 checks aggregate-root event-handler On signatures.

TypeScript and React projects get the same kind of feedback through shared flat-config presets and product-specific ESLint plugins. These are the packages in the current source tree:

PackageSourceBuild gate
@cratis/eslint-configFundamentalsShared consumer and internal presets: TypeScript/React hygiene, BDD spec relaxations for for_* files, and the Cratis license-header rule for product repos.
@cratis/eslint-plugin-arcArcSkips generated Arc proxies and forbids React hook calls inside MVVM view-model classes.
@cratis/eslint-plugin-componentsComponentsForbids root-barrel imports from @cratis/components and raw primereact/dialog imports when the Cratis wrappers should be used.

Compose them in eslint.config.mjs:

import cratis from '@cratis/eslint-config';
import arc from '@cratis/eslint-plugin-arc';
import components from '@cratis/eslint-plugin-components';
export default [
...cratis.configs.consumer,
...arc.configs.recommended,
...components.configs.recommended,
];

Inside the Cratis product repos, CI runs these as lint gates. The common workspace script is yarn g:lint:ci, while package apps like the Chronicle Workbench use yarn lint:ci.

The architecture and coding instructions live in the Cratis AI configuration: C#, TypeScript, vertical slices, React components, BDD specs, Chronicle reactors, Orleans, review agents, and task-specific skills. That layer tells an assistant what good Cratis code looks like.

Code analysis is the enforcement layer. Roslyn analyzers and ESLint rules run after the assistant writes code, catch the parts that drift, and produce concrete diagnostics that can be fixed in the same loop. For AI-assisted development, the combination matters more than either half alone: instructions reduce bad output, build gates make the remaining mistakes visible.