Open source · MIT · .NET 10

Your agent knows XAF.
It has never seen your app.

One command changes that.

An agent that has read every page of the DevExpress documentation still does not know that your Invoice total is calculated from its lines, that ApproveController refuses to run when the period is closed, or that three columns were hidden in the Model Editor and appear in no C# file at all.

It will confidently invent all three. That gap is not solvable by better prompting — it is solvable by reading the code.

The difference, in one line

Same code.
Two very different answers.

A real attribute from a real XAF entity. Ask an agent what it does with and without the extraction, and the gap stops being abstract.

Order.cs MyApp.Module / BusinessObjects / Order.cs
[DefaultClassOptions]public class Order : XPCustomObject{    [Association("Customer-Orders")]

Without the extraction

“It filters the customer lookup. Customer probably has an active flag, so I'll write IsActive = True.”

Confident. Fluent XAF. Wrong — that property does not exist.

With the extraction

“Filters the Customer lookup to unblocked customers. IsBlocked is a bool on Customer, and this is XAF criteria syntax — neither SQL nor a C# lambda — evaluated by the database.”

Read from your source: the property, its type, and the entity it belongs to.

    [DataSourceCriteria("IsBlocked = False")]    public Customer Customer { get; set; }     [PersistentAlias("Lines.Sum(LineTotal)")]    public decimal Total => /* calculated in the database */}

Where this fits

DevExpress teaches the framework.
This teaches your application.

These compose. Install the official skills for how XAF works, keep the documentation server for the official reference, and add this for the one thing neither of them can know.

Three kinds of knowledge an agent needs about an XAF codebase Two are already solved by DevExpress tooling and drawn in grey. The third — what your own application does — is the gap this project fills, drawn in colour. Three things an agent needs to know · two are already solved How XAF works Controllers, actions, views, validation, security, the model. The framework, in general. DevExpress agent-skills What the docs say Every published page, exact API signatures, official guidance and examples. DevExpress Docs MCP What YOUR app does Your 19 entities. The rule that blocks approval. The columns hidden in the Model Editor. Nothing else can know this XAF Logic Explainer reads your source with Roslyn Install all three. They answer different questions.
An agent with the first two writes flawless XAF against entities you do not have.
Reads

Entities and relationships

Properties, types, associations, aggregation, and the XAF attributes that give them meaning. XPO and EF Core, detected from your using statements.

Reads

Controllers and actions

Every action, its target criteria, and the C# that actually runs when someone presses the button.

Reads

Rules and the Model Editor

Validation with its messages, conditional appearance, calculated properties — and the .xafml customizations that exist in no C# file.

How extraction works Source files are parsed as syntax by Roslyn, never compiled, producing a model that is rendered to agent files, an MCP server, or Markdown. Your source is read, never built *.cs *.xafml Roslyn syntax trees only never compiled Extracted model entities · actions · rules navigation · .xafml AGENTS.md · CLAUDE.md MCP server Markdown · JSON Because nothing is compiled, it works on a branch that does not build — which is often exactly when you need to know what the application does.
No DevExpress licence is needed to run any of this: the analyzer never references a DevExpress assembly.

The part nobody reads

An XAF application keeps
its secrets outside the classes.

Read every business object in an XAF solution and you will still be wrong about it. Some of what the application does is not in C# at all, and the rest is in files nobody opens when they are trying to understand a domain.

Screens that ignore their type

A string that renders as a barcode scanner. The custom editor lives in the platform project, beside the module — so nobody reading the business objects ever meets it, and neither does an agent.

Behaviour in JavaScript

The file the editor cannot work without, in neither C# nor XML. It is why a control breaks when somebody renames an asset, and why nothing in the codebase explains it.

Editors changed at run time

There is no custom class to find: a controller reaches into a built-in editor's component model. Nothing on the entity, and nothing in the Model Editor, mentions it.

Migrations that run once

A block guarded by CurrentDBVersion runs at most once for any database, and never again after that. Reading today's code cannot recover what it did — so an agent asked why a column holds what it holds will invent a reason.

The custom editors section: a barcode scanner property editor, what it renders, the alias XAF matches on, its base type, and the JavaScript file it depends on — followed by a table of built-in editors reconfigured at run time by a controller.
Read from the platform project. The alias constant is declared in the module and resolved across the solution, because reading either project alone resolves nothing.
The migrations section: two version-gated updater blocks, each with the version it upgrades to, the schema phase it ran in, the condition, the methods it calls and the code itself.
Including the comment above the block — usually the only surviving record of why, and the question anyone reading a migration actually has.

What runs when you open a screen

The screens are in no file.
Neither is what loads onto them.

XAF generates a list, a detail and a lookup view for every business class, plus a list view for every collection — and the Model Editor stores only the ones somebody changed. Grep your source for Patient_Prescriptions_ListView and you find nothing. It is still a screen your users open.

Which controllers run there is decided at run time, by four conditions XAF ANDs together: nesting, view type, object type and view id. Every one of them is unrestricted when unset, so a controller that sets none loads onto every screen you have — and almost nobody knows which of theirs do.

The screens section of a generated explainer: the five views XAF generates for one business class, each naming the controllers that activate on it and the condition that made each one match, with the framework's own controllers folded away behind a single line.
A transcription of ViewController.IsFitToView, condition by condition, against an inventory built from the framework's own id generators. Each match records why, so the answer can be checked instead of trusted.

Two layers, kept apart

What your team wrote, in full. What XAF provides, folded behind one line — there is a great deal of it, and it is not yours to change. The distinction is the whole point when somebody asks you to change what a screen does.

What it refuses to claim

A controller listed here can still switch itself off through Active["reason"], which depends on the data and the user. This is what XAF loads onto a screen — and anything unreadable from source is listed apart, with the reason, rather than quietly counted as “runs everywhere”.

The limit

Your reports are not in your repository.
Saying so is the feature.

Every other section on this page claims we found something you could not see. This one is the opposite, and it is the half that decides whether the rest is worth trusting.

Reports V2 lets your users design reports at run time. Those are stored as rows in your database, not as files — so no tool that reads a repository can see them, this one included. An application with forty reports and none in its source is not unusual. It is what a reporting setup looks like when people actually use it.

What is in the source

Each registered report: what it is over, the filter inside its layout, its calculated fields, the expressions behind its labels, and the parameters dialog it opens with — down to the GetCriteria() that turns the answers into a filter. From designer code, from a .repx, or from the report's own constructor.

What the list is

Three different sentences, not one. No ReportsModuleV2 and the list is all of them. Module registered and it is a lower bound. Module registered with nothing in source and the true number is unknown, not zero — which is the most common case of the three.

Layouts nothing registers

A shop that designs reports outside Visual Studio keeps the .repx exports in a folder beside the module and imports them by hand. Real work that no line of code mentions — listed as unregistered rather than skipped.

The same rule, elsewhere

If the DevExpress catalog on your machine describes 26.1 and your application declares 23.2, every framework answer says so, with both numbers. A controller can be introduced or retargeted between releases: close enough is not the same as certain.

That third sentence is where a confident tool does real damage. An agent told “this application has no reports” will design as though none can exist — for an application whose users built forty. So AGENTS.md says it outright:

This application registers ReportsModuleV2 and declares no report in source.
That is not the same as having none: users design reports at run time and they are stored in the database, out of reach of anything reading this repository.
The number is unknown, not zero — do not tell anyone this application has no reports.

A tool that never says “I cannot see that” is not more capable. It is less checkable.

The output

An AGENTS.md is read on every request.
So most of it isn't in there.

That file is prepended to every question an agent answers in your repository — its size is a tax paid forever. Seventy kilobytes of entity detail there would crowd out the thing the user actually asked.

So the output is tiered.

What a full dump costs an agent's context on every request Putting all documentation in AGENTS.md consumes most of the context window before the user has asked anything. Tiering leaves that room free. Every request the agent answers in your repository Everything in AGENTS.md the question application documentation · every entity, every handler Paid on every request, forever — before anyone has asked anything. Tiered index room for the question, the files being discussed, and the answer detail · opened only when a question needs it ~11 KB of 72
Roughly to scale, against a modest context budget. The index is the part that changes an answer; the rest is reference material, and reference material can be looked up.

The most valuable part is the smallest. The index opens with ground rules — which ORM this application uses and which APIs therefore do not exist in it, that the inventories are complete so anything absent genuinely is, and that some behavior lives in XML rather than in code.

CLAUDE.md and .github/copilot-instructions.md are written too. Anything you added by hand survives regeneration, and regenerating twice produces identical bytes.

Live, not a snapshot

Or let the agent
ask questions directly.

A generated file is a snapshot. The MCP server is a connection: twelve tools your agent calls against the source while you are still changing it, so it cannot go stale.

xaf_overview

What this application is, and the complete list of everything in it.

xaf_entity

Every property, relationship, rule and calculation on one entity.

xaf_controller

What an action does — including the C# that runs when it fires.

xaf_walkthrough

How one business process works end to end: what runs, in what order, and what the walk could not follow.

xaf_reports

What a report shows and the filter inside it — and whether the list is all of them or a lower bound.

xaf_view · xaf_editors · xaf_migrations · xaf_search · xaf_rules · xaf_model · xaf_refresh

What loads onto one screen, the custom editors, what ran once against a live database, a business term, what the app enforces, the Model Editor, and a re-read of the source.

Ask for something that isn't there, and the answer is the useful one:

There is no entity called 'PurchaseOrder' in this application.
This is the complete list of 19 entities, extracted from the whole source tree: …
If the user expects 'PurchaseOrder' to exist, it has not been created yet.

A bare “not found” invites an agent to assume it looked in the wrong place and invent the type anyway. Extraction reads the whole tree, which is what makes the stronger claim true.

And for the person who inherits it

Most teams have never seen
their own domain model.

It exists as association attributes scattered across twenty files, and the map of it lives in one person's head — which is exactly the knowledge that leaves when they do.

The domain model of a sample XAF application. Hovering an entity dims everything it does not touch, leaving only its own relationships lit.
Real output. Hover an entity and everything it does not touch fades; purple means deleting the parent deletes the child.

xaflogic explain writes one self-contained HTML file — no server, no build step, no request to the network. It opens from an email attachment on a machine with no internet, which is how handovers actually happen.

Alongside the map: every entity and what each property is, every action with the code it runs, validation with the message the user will actually see, and an index of every criteria expression in the application — a dialect that is neither SQL nor C# and is otherwise collected nowhere.

An entity card: every property with its type, which ones are calculated and the expression behind each, and the relationships with the ones whose parent owns the child marked as owned.
Purple is anything computed rather than stored — the properties that have no column and cannot be searched for in the database.
The criteria index: every criteria expression in the application in one table, each with the attribute and entity it came from.
Appearance rules, validation and lookup filters, collected from attributes spread across the source — the working examples an agent needs to write a criteria string that parses.

And for the person who has built forty of them

You already solved this.
In a project you cannot remember.

Ten years of XAF work for different clients, and somewhere in there is the class you are about to model again. Not a similar one — the same one, thought through properly, with the two properties you are going to forget this time.

xaflogic wiki reads every project you have configured into one page and computes what they have in common. There is nowhere in that page to type a sentence about the collection, which is the point: a hand-written summary of nine applications is wrong the day the tenth arrives, and nobody notices.

Three client applications on a ring, with the classes all three model drawn between them. AuditedEntity, Cliente and Factura sit at the centre, each joined by a line to all three applications.
Real output. Each class sits at the average direction of the applications that model it, at a distance set by how much they agree — so a class every application has falls to the centre. The middle is your common ground; the rim belongs to one client.

Classes you modelled more than once

Property by property, side by side, so you can see which application models Cliente in the most detail before writing it a fourth time.

The layer you wrote yourself

Base classes carried from one application into another: your own framework, the one that was never written down.

The same name, two shapes

Total is a decimal in one application and a double in another. Nothing is broken. It is how a total ends up two cents out.

Names you keep

The vocabulary of your applications, which nobody wrote down and everybody who joins has to learn by reading code.

What the wizard gave everybody

ApplicationUser is in every XAF solution made since 2021, because the Project Wizard writes it. Two applications holding the same one did not build it twice, so it is shown and left out of the count. Extend it in two of them and it comes back.

A name, and nothing else

An Identity ApplicationUser and an XAF one share a name and not one property. The page says so, rather than leaving you to work it out row by row from a comparison where everything differs.

Beside the map, two more pictures only a corpus can draw: which two of your projects are most alike, as a grid you click to hold the page to just those two, and the DevExpress releases your estate is spread across — with the one your framework catalog actually describes marked on it.

Two classes are matched by name. So the wiki tells you they share a name — the comparison beside it is what tells you whether they share an idea. It says which of those two things it knows, on the page, next to the finding.

Which is why the count is worth less than what it excludes. A class every XAF solution was given is not reuse; a pair that shares a name and no base, no contract and not one property was probably never the same idea. Both are still on the page, said out loud — one shared property name is enough to withdraw the second remark, because a concept rebuilt on another framework is still one concept.

Optional, for licensees

Tell your code apart
from DevExpress's.

Reading source alone leaves one question unanswerable: is DeleteObjectsViewController something your team wrote, or something DevExpress ships? xaflogic catalog build reads your own installation and answers it.

~850
framework types
50
assemblies read
0
committed to your repo

“This extends built-in behavior”

A controller deriving from DeleteObjectsViewController is changing how deletion works application-wide, not adding a feature beside it.

“This attribute is yours”

An attribute in neither XAF nor .NET was invented by your team — its meaning lives in your codebase and in no documentation anywhere.

Install

Two minutes.

A .NET global tool. No account, no API key, no server.

terminal
$ dotnet tool install -g XafLogicExplainer.Cli # Write the context files at your solution root$ xaflogic agents --project "C:\MySolution\MyApp.Module" ✓ AGENTS.md✓ CLAUDE.md✓ .github\copilot-instructions.md+ 7 detail files in .xaflogic/ # Ask your agent something only this codebase could answer. 

Prefer the agent to query live? Install the plugin:

claude code
/plugin marketplace add peopleworks/XAFLogicExplainer/plugin install xaf-logic-explainer@peopleworks-xaf # Pair it with the official DevExpress skills:/plugin marketplace add DevExpress/agent-skills/plugin install dx-xaf@DevExpress-agent-skills