Skip to content

v0.15.0 — how it works, not only what exists

Choose a tag to compare

@peopleworks peopleworks released this 23 Aug 06:37
· 38 commits to main since this release
b058d35

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, so xaf_entity and
    xaf_controller handed 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, and Customer sits 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 a file:line a reader can check.

  • The walk that decides what belongs to one process ([#23], phase 1). ProcessSlice.From takes
    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 record virtual/abstract and override, 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 walkthrough and xaf_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 a file: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_walkthrough is 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.Core still 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.json that xaflogic diff does, 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]). AppearanceAttribute has 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]). AppearanceItemType was never read,
    so a rule disabling the Delete action 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 said ViewItem. 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 wrote Criteria = named, which is the whole reason the suite agreed. 402 tests.

  • An appearance rule written on a property is read ([#21], thanks [@MBrekhof]).
    AppearanceAttribute is usable on a class or on a property, and the documentation teaches the
    property form first: a rule on UnitPrice and 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 no TargetItems of its own now
    records the property it was written on, which is what the class spelling states outright; an
    explicit TargetItems is 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 on Id alone, 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 on always and 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 on Id alone 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:
    --enrich refused 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 for COPILOT_API_TOKEN.
    There is now one resolver shared by both, taking the first route that is configured: --api-key
    on the command line, then OPENAI_API_KEY or ANTHROPIC_API_KEY in the environment, then a
    PeopleWorks Copilot account — which still works untouched, as one option among several rather
    than the gate. --ai-base-url reaches any OpenAI-compatible endpoint, including a local one, and
    --ai-model names 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
    Recalculate that 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.