Skip to content

Add xaflogic wiki — every project in one page, and what they share - #49

Merged
peopleworks merged 1 commit into
mainfrom
feat/cross-project-wiki
Aug 24, 2026
Merged

Add xaflogic wiki — every project in one page, and what they share#49
peopleworks merged 1 commit into
mainfrom
feat/cross-project-wiki

Conversation

@peopleworks

Copy link
Copy Markdown
Owner

An explainer answers how does this application work. Someone who has delivered XAF applications to clients for ten years has a question that cannot be asked of one application at a time: have I built this before?

xaflogic wiki reads every configured project into one self-contained HTML page and computes what they have in common. There is nowhere in that page to type a sentence about the collection — deliberately. A hand-written summary of nine applications is wrong the day the tenth is added, and nobody notices.

xaflogic projects add --name pwLegalOffice --project "C:\Clients\Legal\pwLegalOffice.Module"
xaflogic projects add --name Presupuesto   --project "C:\Clients\Budget\PWPresupuesto.Module"
xaflogic wiki --open

What it computes

Classes modelled more than once Property by property, a column per application, richest first
The layer you wrote yourself Base classes carried between applications — your own framework
The same name, two shapes Total a decimal here and a double there
Names you keep The vocabulary nobody wrote down
Shared modules What RequiredModuleTypes says they all need

Plus per-application detail, search across everything, and a filter that shows only what touches one project.

Two decisions that came from real projects, not fixtures

Run over six real XAF applications (405 entities, 111 controllers), the first output had the signal buried:

  • Double and double are one type. Reporting them as a disagreement is a false accusation, and a tool that makes one stops being believed about the true ones. Same for a nullable annotation on a reference type. decimal? beside decimal is a real difference and is kept.
  • A name holding a different collection per entity is vocabulary, not a conflict. XPCollection<Cobro> beside XPCollection<CobroDetalle> is what ordinary words do. It was burying Total: decimal vs double, UnitPrice: double vs decimal and Activo: string vs bool — the ones somebody would act on. Scalar disagreements now sort first, so the cap can never be what removes them.

One more came from opening the page rather than reading the diff: the comparison table silently cropped its last column. That is the failure that matters — a reader would have believed the columns they could see.

A rule worth stating

A base class is listed as yours only when its own source was read in one of the projects. No list of DevExpress type names is involved, so nothing rots when DevExpress renames something. On the six real applications this returned zero — everything derives straight from XPObject, XPCustomObject, XPLiteObject. That is itself the finding, and the page says it rather than showing a heading over nothing.

The limits section says the rest out loud: classes are matched by name, so the wiki knows they share a name and the comparison is what tells you whether they share an idea; a base class in a library you did not add to the wiki is absent, not framework; and report counts stay lower bounds, per application, exactly as in 0.16.0.

Also

projects add no longer requires --resource-name. It names a PeopleWorks Copilot resource — one publishing target among several, and irrelevant to wiki, explain, agents and mcp, all of which read the configured list and write locally. Requiring it made the multi-project list unreachable without an account somewhere. Defaults to the profile name.

Verification

  • 498 tests pass (40 new: 28 on the analysis, 12 against the generated HTML)
  • The rendering tests assert the page asks the network for nothing, that no citation carries a machine path, and that every empty state reads as a finding
  • Generated over six real applications and opened in a browser: filter, search, and the scrolling comparison verified in the DOM — 71 matches when filtered to one project, zero leakage

🤖 Generated with Claude Code

An explainer answers "how does this application work". A developer with ten
years of client work has a question no single-application tool can be asked:
have I built this before? Somewhere there is the class about to be modelled
again — not a similar one, the same one, thought through properly, with the two
properties this time will forget.

`xaflogic wiki` reads every configured project and computes what they have in
common. There is nowhere in the page to type a sentence about the collection.
That is deliberate: a hand-written summary of nine applications is wrong the day
the tenth is added, and nobody notices.

What it computes:

- Classes modelled more than once, property by property with a column per
  application, so the richest version is the one opened before writing it again.
- The layer written here and reused: base classes carried between applications.
  A base type qualifies only when its own source was read in one of the
  projects, so no list of DevExpress type names is involved and nothing rots
  when DevExpress renames something. On six real applications this returned
  zero, which is itself the finding — everything is rebuilt from XPO primitives.
- The same name, two shapes: `Total` a decimal here and a double there.
- Names kept, and modules more than one application requires.

Two decisions came from running it over six real applications rather than
fixtures. `Double` and `double` are one type and reporting them as a
disagreement is a false accusation — a tool that makes one stops being believed
about the true ones; so are nullable annotations on reference types. And a name
holding a different collection per entity is vocabulary, not a conflict:
`XPCollection<Cobro>` beside `XPCollection<CobroDetalle>` buried `decimal`
beside `double` until they were separated.

Also from opening the page rather than reading the diff: the comparison silently
cropped its last column, which is the failure that matters — a reader would have
believed the columns they could see.

`projects add` no longer requires `--resource-name`. It names a PeopleWorks
Copilot resource, which is one publishing target among several and irrelevant to
wiki, explain, agents and mcp; requiring it made the multi-project list
unreachable without an account somewhere.

40 tests: 28 on the analysis, 12 against the generated HTML — that it asks the
network for nothing, that citations never carry a machine path, and that each
empty state reads as a finding rather than a heading over nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W45tzJFX3NoSrk7svtQeKT

var wikiCorpus = CorpusAnalyzer.Analyze(wikiApplications);
var wikiHtml = new WikiGenerator(ThisAssemblyVersion()).Generate(wikiCorpus, wikiTitle);
var wikiFile = wikiOutput ?? Path.Combine(Directory.GetCurrentDirectory(), "xaf-wiki.html");
Comment on lines +144 to +153
foreach (var entity in app.Project.Entities)
{
if (string.IsNullOrWhiteSpace(entity.ClassName) || !seen.Add(entity.ClassName))
continue;

if (!byName.TryGetValue(entity.ClassName, out var list))
byName[entity.ClassName] = list = [];

list.Add((app, entity));
}
Comment on lines +278 to +290
foreach (var declaration in declarations(app))
{
if (string.IsNullOrWhiteSpace(declaration.ClassName))
continue;

declaredAt.TryAdd(declaration.ClassName, new CorpusSite
{
Application = app.Name,
Slug = app.Slug,
Owner = declaration.Owner,
Citation = SourceCitation.Of(app.Project, declaration.FilePath, declaration.Line),
});
}
Comment on lines +359 to +368
foreach (var action in controller.Actions)
{
if (string.IsNullOrWhiteSpace(action.ActionId))
continue;

if (!byId.TryGetValue(action.ActionId, out var list))
byId[action.ActionId] = list = [];

list.Add((app, controller, action));
}
Comment on lines +420 to +429
foreach (var property in Declared(entity))
{
if (string.IsNullOrWhiteSpace(property.Name))
continue;

if (!byName.TryGetValue(property.Name, out var list))
byName[property.Name] = list = [];

list.Add((app, property));
}
Comment on lines +518 to +527
foreach (var name in declared(app).Distinct(StringComparer.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(name))
continue;

if (!byName.TryGetValue(name, out var list))
byName[name] = list = [];

list.Add(app.Name);
}
@peopleworks
peopleworks merged commit 9d6669b into main Aug 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants