v0.15.0 — how it works, not only what exists
How it works, not only what exists.
Every release until now answered the same shape of question. What entities does this application
have. What does this controller do. What does that rule forbid. All of it true, all of it
declarations — and a business process is not a declaration. It is a path across several of them, and
nothing in the extractor walked a path.
xaflogic walkthrough --from ApproveOrder walks it. The scope is computed, by a bounded
breadth-first traversal, and not chosen by a language model — because a model asked what belongs in
"the approval process" answers authoritatively, in a form nobody can check, and is wrong in the
places that look exactly like the places it is right. The Mermaid diagram is emitted from that
traversal arrow for arrow, for the same reason and more sharply: a diagram is believed at a glance,
so an invented edge in one is worth less than no diagram at all.
The two halves that make it honest are the ones that took the most care. A call the walk cannot
follow is printed rather than dropped — a virtual method is followed to the declaration written
beside the call and reported with every override that may replace it, and the document says outright
that the bodies it never entered mean entities missing from the account. And --since reports what
changed in that one process against a stored snapshot, which is the question no conversational
agent can answer, because none of them has a yesterday.
--narrate is opt-in and, deliberately, the least load-bearing thing here. The model receives the
numbered steps and the code behind them; the only prose that reaches a reader is a paragraph it
managed to key to a step that exists. A document generated with an empty narration is
byte-for-byte the one generated with none.
Also in this release: every declaration now says where it is, file:line, in the extraction and in
the MCP tools — the foundation the walkthrough needed and worth having on its own, since the tools
used to hand an agent a name and leave it to search for it. Four appearance-rule defects, two of
them found by [@MBrekhof]. Any model can now answer, and a key is enough.
And the Markdown we generate is Markdown, which took two goes. A seed method's source was wrapped in
a <details> fold that collapses on GitHub and nowhere else ([#28]). Then, running the output
through a real Word converter, [@MBrekhof] found the second half of the same defect: a generic base
type was written bare, and <DetailView> is an inline HTML tag rather than a block, so an export
drops it and github.com's sanitizer strips it — every controller read as deriving from plain
ViewController ([#36], fixed in [#39]). Type names are written as code now, and the guard was
widened from lines that open with < to a tag anywhere on a line. The route from an extraction to
a Word document is written down in the README, and there is deliberately no exporter here: reading
an XAF application needs no DevExpress, and that stays true.
402 tests, zero warnings.
Added
-
Entities, controllers, actions and methods say where they are declared ([#23], first step).
The extraction knew which file a class was in and nothing narrower, soxaf_entityand
xaf_controllerhanded an agent a name and left it to search the file for it. Each now carries a
one-based line, taken from the identifier token rather than from the declaration's span — a
span begins at the first attribute, andCustomersits behind a doc comment and four of them, so
the two answers are four lines apart and only one of them is the line anybody means. Actions and
methods carry their own file as well as their own line, because a partial controller's members
need not be declared in the file the controller is cited at. The MCP tools name the file once and
then cite members by line alone; a member in a different file is given in full, which is exactly
the case where a reader would otherwise open the wrong one. Prerequisite for the walkthrough,
where every claim is supposed to carry afile:linea reader can check. -
The walk that decides what belongs to one process ([#23], phase 1).
ProcessSlice.Fromtakes
an action, a controller method, a controller or an entity by name and walks outward from it —
breadth-first, bounded by depth, syntax-only like everything else here. It follows an action to
the handler it runs, a method to the methods it calls and the entities it names, a controller to
the entities it is activated for, and an entity to the rules that govern it. Every node carries
the file and line a reader can open.
The scope is computed rather than asked for, and that is the governing decision: a model asked
what belongs in "the approval process" answers authoritatively, in a form nobody can check, and is
wrong in the places that look exactly like the places it is right.
A call it cannot follow is printed, not dropped. A virtual declaration is followed to what is
written beside the call, and reported with every override that may replace it — which is honest
about the consequence, because the bodies that were not entered mean entities missing from the
slice. So is the depth bound: a walk that ran out of things to reach is a whole process, and a
walk that stopped at its limit is a view of one, and rendering them identically is how a document
claims completeness it does not have.
Methods now recordvirtual/abstractandoverride, which is what makes that distinction
possible at all. Nothing consumes the slice yet — the Mermaid diagram, the document, the CLI
command and the MCP tool are phase 2. -
xaflogic walkthroughandxaf_walkthrough([#23], phase 2). The slice becomes a document,
and the feature is now usable: a Mermaid diagram, everything that takes part with the place it is
declared, the ordered steps each citing afile:line, the calls the walk could not follow, and
what the walk deliberately is not. In both languages, offline, with no API key and no network.
The diagram is emitted from the walk's own edge set — node for node, arrow for arrow. Nothing
decides what to draw. Ask a model for a Mermaid diagram of a process and it will produce one,
including edges that do not exist, drawn with a confidence indistinguishable from the true ones,
in a format whose whole value is that a reader believes it at a glance. A test counts the arrows
against the edges, because an invented one is exactly what a spot check of a diagram that looks
right would miss.
xaf_walkthroughis the first MCP tool that answers a question about a process. The other ten
return atoms, so an agent asked how something works has to guess which atoms to fetch and then
guess whether it has them all — and the guess that stops one atom early produces a confident
answer with a step missing from it. -
xaflogic walkthrough --narrate([#23], phase 3). Opt-in prose over a walk that has already
been computed: one paragraph on what the process is for, and a sentence or two under each step,
each sitting directly beneath the citation it belongs to.
The model narrates; it does not discover. It receives the numbered steps and the code behind
them, and the only thing that reaches a reader is a paragraph it managed to key to a step that
exists — a paragraph keyed to step 99 of a nine-step process is dropped before rendering, and so
is fluent prose attached to no step at all. The point is not that such a sentence would probably
be wrong; it is that nobody could check it, and an ordinary reader cannot tell a fluent sentence
about real code from a fluent sentence about code that is not there.
The model is also told what the walk could not follow, so it does not narrate its way over the one
gap the analysis already knows about.
Failure costs prose and not the document: no key, or a provider that does not answer, prints why
and writes the walkthrough anyway. Phases 1 and 2 stand entirely on their own, which is what makes
the model optional rather than load-bearing — and a test pins that a document generated with an
empty narration is byte-for-byte the one generated with none.
XafLogicExplainer.Corestill references nothing but Roslyn: narration arrives at the generator
as plain text keyed to steps that already exist. -
xaflogic walkthrough --since([#23], phase 4 — the last one). Re-walks the same process over
a stored snapshot and reports what is different about this process: a step added, a rule now
governing it, a branch gone, a body rewritten, and a call the trace can no longer follow. Reads
the same_Previous.jsonthatxaflogic diffdoes, or any snapshot given by path.
This is the part no conversational agent can imitate, because none of them has a yesterday. Asked
what changed in the commission calculation since the last release, a model can only re-read
today's code and describe it fluently.
Comparing two walks by their node sets alone would have missed the most ordinary change there is —
somebody edits a method body and leaves every call in it alone — so each node now carries a
fingerprint of its own substance: a method's body, a rule's condition and effect, an action's
caption and criteria. Whitespace is collapsed first, so reformatting is not reported as a change
of behaviour. A controller and an entity have no fingerprint; what matters about them is elsewhere
in the slice, and giving them one would report the same edit twice.
Three states that had to stay distinct: the process is unchanged, the process did not exist at the
snapshot, and no snapshot could be read — the last one stops the run rather than writing a
document with the section missing, because an absent section reads exactly like "nothing changed".
Fixed
-
An appearance rule keeps its condition however the attribute was written ([#22], reported by
[@MBrekhof]).AppearanceAttributehas three constructors and two pass the criteria by position —
(id, criteria)and(id, appearanceItemType, criteria). Only the named form was read, so a rule
written either of the other two ways was extracted with no condition and then documented as
applying always, which is the strongest claim a rule can make, printed about rules whose whole
purpose is a condition. The criteria is the last positional argument in both overloads that carry
one, so both are read without knowing which constructor was called; a named argument still wins.
The expression index — the page that introduces itself as every distinct expression in the
application — heals on its own, because it already gathered appearance criteria and there were
none to gather. -
A rule over an Action is no longer called a field ([#22]).
AppearanceItemTypewas never read,
so a rule disabling theDeleteaction was documented as governing a column called Delete — a
confident sentence about something that does not exist. It is written two ways in real code:
positionally as the enum, and by name as a string, which is the form the DevExpress examples use;
both are read and normalised to one value. Absent means the XAF default,ViewItem, and is
recorded as absent so a rule that said nothing can be told from one that saidViewItem. The
Markdown now says actions or layout items where it applies, the MCP tool says
Delete (actions), and both diffs treat the item type as part of a rule's identity — a rule
repointed from a column to an action of the same name changes nothing else. -
New fixture,
AppearanceSolution, whose rules are written every way the attribute allows. Every
existing fixture wroteCriteria =named, which is the whole reason the suite agreed. 402 tests. -
An appearance rule written on a property is read ([#21], thanks [@MBrekhof]).
AppearanceAttributeis usable on a class or on a property, and the documentation teaches the
property form first: a rule onUnitPriceand a rule on the class naming
TargetItems = "UnitPrice"are two spellings of one rule. Only the class spelling was read, so
the other produced nothing at all — and an entity's section is presented as its complete
inventory, so a rule governing a property and reported nowhere left the reader concluding the
property was unconditionally editable. A property rule that names noTargetItemsof its own now
records the property it was written on, which is what the class spelling states outright; an
explicitTargetItemsis left alone. Measured on a 196-entity application: 25 rules to 33. -
Two unnamed appearance rules stay two rules through the fold ([#21], thanks [@MBrekhof]).
The fold keyed them onIdalone, so an empty identifier made them one rule and the second was
dropped in silence. An empty identifier is ordinary rather than an omission — a rule written on a
property already says what it governs. -
An appearance rule with no name, or no criteria, is rendered as what it is. The Markdown and
the MCP tool printed- **** — when ``:— an empty bold span where the identifier goes, and a
condition that reads as though it failed to load. In XAF a rule that declares no criteria is
permanently active, which is the stronger of the two claims; the HTML explainer had already
settled onalwaysand the other two never received it. Reachable before rules were read off
properties — one unnamed rule per class is enough — and made ordinary by reading them. -
The diff reports appearance rules that were added, removed, edited or repurposed. It keyed
them onIdalone and collected them into a set, which failed three ways from the one key: every
unnamed rule in an application collapsed into a single entry, so adding or removing one reported
no change; a rewritten criteria kept its identifier, so the edit reported nothing; and a rule
changed from disabling a field to hiding it reported nothing either, which is the whole of what
an appearance rule does. Probed at the time: an application went from two declared rules to
three and the diff reported zero changes.
Changed
-
Any model will do, and a key is enough ([#24]). Every AI feature reached PeopleWorks Copilot
for its credentials — not for a key the user had configured, but for the model's key, fetched
from an account. On a public MIT project that meant no outside user could run any of them:
--enrichrefused without an API URL and token and told the reader to configure credentials for
a service they had never heard of, and the Description Annotator asked forCOPILOT_API_TOKEN.
There is now one resolver shared by both, taking the first route that is configured:--api-key
on the command line, thenOPENAI_API_KEYorANTHROPIC_API_KEYin the environment, then a
PeopleWorks Copilot account — which still works untouched, as one option among several rather
than the gate.--ai-base-urlreaches any OpenAI-compatible endpoint, including a local one, and
--ai-modelnames the model. Someone with none of them configured is now told all four.
A key is never read from or written to the configuration file: the endpoint and the model name
are settings, a key is a secret, and that file lives in a home directory that gets copied around. -
The Markdown we generate is Markdown ([#28]). A seed method's source was wrapped in a
<details>fold. That collapses on GitHub and nowhere else: in a Word or PDF export, in a plain
Markdown viewer, and to a model reading the file, the wrapper is literal text and the fold's
label — "Source code of PopulateStatuses" — stops being a label and becomes a line of markup. It
is now a heading, which survives the trip and takes its place in the document outline. The fold
is lost on GitHub; these files are read far more often than they are scrolled. Found by walking
the output against the Markdig converter in [mcpOffice], whose documented behaviour for an HTML
block is to emit it as plain text — every other construct we write already maps to a real Word
equivalent, so this one call site was the whole distance between an extraction and a document
somebody can hand over.
Internal
-
First tests over
ProjectDiffEngine, which is why the key above survived. -
Every sample project's Markdown is now checked, in both languages, for a line that opens raw HTML
outside a code fence. -
Citations are checked by reading the fixture back off disk: the cited line must really contain the
declaration, across every sample project. It is the only assertion that catches an off-by-one or a
span that starts at an attribute. -
New fixture,
WalkthroughSolution, whose proportions are its point: one action, one handler, and a
Recalculatethat two controllers override — so a walk reporting only what it can resolve
produces a confident, complete-looking account of a process whose body it never saw. No existing
fixture could reach that case.