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.
Why build gates, not docs alone
Section titled “Why build gates, not docs alone”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.
Roslyn analyzer packages
Section titled “Roslyn analyzer packages”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.
21 rules for event-sourcing constructs. Ships with the Cratis.Chronicle package.
Rules for commands and read models. Ships with Arc.Core.
Aggregate-root event-handler rules for Arc-on-Chronicle. Ships with the Chronicle integration.
Chronicle event-sourcing rules (CHR####)
Section titled “Chronicle event-sourcing rules (CHR####)”The Chronicle analyzer enforces the event-sourcing shapes — events, projections, reducers, reactors, and constraints. A representative slice:
| Rule | Catches |
|---|---|
CHR0001 | A type appended to an event sequence that’s missing [EventType] (quick fix adds it) |
CHR0004 / CHR0006 | A reactor / reducer method whose signature doesn’t match an allowed shape |
CHR0015 / CHR0017 | A projection or constraint with side effects (injecting ICommandPipeline / IEventLog) |
CHR0016 / CHR0018 | Imperative code inside a Define() that should only contain builder calls |
CHR0021 | An 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.
Arc command & read-model rules (ARC####)
Section titled “Arc command & read-model rules (ARC####)”The Arc.Core analyzer validates the model-bound CQRS shapes — the ones the writing-correct-examples discipline keeps reiterating, now checked by the compiler:
| Rule | Catches |
|---|---|
ARC0001 | A [ReadModel] query method that doesn’t return the read model (or an allowed wrapper) |
ARC0002 | A command-like type missing its [Command] attribute |
ARC0003 | A Handle() method declared somewhere other than the command type itself |
ARC0004 | A [Command] type with no public Handle() method |
And for Arc on Chronicle, ARCCHR0001 checks aggregate-root event-handler On signatures.
ESLint packages
Section titled “ESLint packages”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:
| Package | Source | Build gate |
|---|---|---|
@cratis/eslint-config | Fundamentals | Shared 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-arc | Arc | Skips generated Arc proxies and forbids React hook calls inside MVVM view-model classes. |
@cratis/eslint-plugin-components | Components | Forbids 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 AI layer and the enforcement layer
Section titled “The AI layer and the enforcement layer”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.