Skip to content

Commit 3d2dc09

Browse files
authored
Merge pull request #46 from peopleworks/feat/report-rendering
Write the reports down, and say what the list is
2 parents 59a419b + 1aaee0c commit 3d2dc09

9 files changed

Lines changed: 809 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

1010
### Added
1111

12+
- **The reports are written down, and the list says what it is** ([#37], the rendering half). They
13+
now appear in the Markdown pages, in `AGENTS.md`, and through a new `xaf_reports` MCP tool — each
14+
with what it is over, the filter inside its layout, its calculated fields and bound expressions,
15+
and the parameters dialog it opens with, including the `GetCriteria()` that turns the answers into
16+
a filter.
17+
18+
**The sentence under the heading carries more than the list.** With `ReportsModuleV2` registered,
19+
users design reports at run time and those are stored as database rows, so an application with
20+
forty of them and none in its repository is the ordinary case: checked against a production
21+
application that registers the module, sets `ReportStoreMode.XML`, and contains no report in
22+
source at all. Printing "no reports" there is not an incomplete answer but a wrong one, and the
23+
more use an application makes of reports the wronger it gets. Three states are told apart — the
24+
module absent and the list complete, the module present and the list a **lower bound**, and the
25+
module present with nothing in source, where the number is reported as *unknown rather than zero*.
26+
An application that registers nothing and ships no layout gets no section, because there "no
27+
reports" is the default rather than a finding.
28+
29+
Two things the generated document showed that reading the code would not have. A layout kept
30+
beside the module rather than inside it was cited by its **absolute path** — the drive of whichever
31+
machine ran the extraction, in a file meant to be committed; citations now fall back to the
32+
solution root and then to the file name, never to a path that is wrong everywhere but here. And
33+
two registrations sharing one `.repx` printed the filter, the bindings and the whole of
34+
`GetCriteria()` twice, burying the only thing that differs between them; the second now points at
35+
the first.
36+
1237
- **The reports an application declares are read** ([#37], phases 1–3 — the extraction; the
1338
Markdown and MCP rendering follow separately). Reports V2 leaves four kinds of trace in a
1439
repository, all syntax, and none of them was read: the registration

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ Started from a solution directory it finds the XAF module by itself, so neither
186186
| `xaf_model` | Model Editor customizations, which exist in no C# file |
187187
| `xaf_editors` | Custom editors, the JavaScript they need, and built-in editors changed at run time |
188188
| `xaf_migrations` | What ran once against a live database, and the comment explaining why |
189+
| `xaf_reports` | What each report is over, the filter inside it, and the dialog it opens with |
189190
| `xaf_view` | Everything loaded onto one screen — which controllers activate, and why |
190191
| `xaf_walkthrough` | **How one process works end to end** — what runs, in what order, and what it could not follow |
191192
| `xaf_refresh` | Re-read the source (changes are detected automatically) |
@@ -388,9 +389,9 @@ applications. The agent-facing surface is what is landing now, in the open.
388389
|| **`AGENTS.md` / `CLAUDE.md` / Copilot instructions** — zero infrastructure, works for everyone |
389390
|| **`xaflogic explain`** — one self-contained HTML page, for a person rather than an agent |
390391
|| Pluggable publishing targets (`IDocumentationSink`) |
391-
|| **MCP server**11 tools, live against your source |
392+
|| **MCP server**12 tools, live against your source |
392393
|| **Installable Claude Code plugin** with skill and MCP server |
393-
|| **445 tests** over synthetic XPO and EF Core fixtures — no DevExpress needed |
394+
|| **457 tests** over synthetic XPO and EF Core fixtures — no DevExpress needed |
394395
|| **DevExpress ground-truth catalog**, generated locally by licensees |
395396

396397
PeopleWorks Copilot, where this tool grew up, is now one sink among several rather than the

src/XafLogicExplainer.Core/Generators/AgentContextGenerator.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public string GenerateIndex(ExtractedProject project, IReadOnlyList<string> deta
7474
WriteCriteriaExamples(sb, conventions);
7575
WriteEditors(sb, project);
7676
WriteMigrations(sb, project);
77+
WriteReports(sb, project);
7778
WriteConventions(sb, project, conventions);
7879
WriteRecipes(sb, project, conventions);
7980
WriteDetailPointers(sb, detailFiles);
@@ -547,6 +548,67 @@ private static void WriteEditors(StringBuilder sb, ExtractedProject project)
547548
/// column holds what it holds, an agent will reason from code that runs today and invent a
548549
/// cause. The real one ran once, years ago, and only the updater remembers it.
549550
/// </remarks>
551+
/// <summary>
552+
/// The reports, and the reason their number may be larger than it looks.
553+
/// </summary>
554+
/// <remarks>
555+
/// The bound is the point of this block, not the list. An agent that reads "no reports" builds
556+
/// as though none exist, and with <c>ReportsModuleV2</c> registered that is the normal state of
557+
/// an application whose users design their reports at run time — stored as database rows, which
558+
/// nothing reading source can reach. The list is worth having; the sentence is worth more.
559+
/// </remarks>
560+
private static void WriteReports(StringBuilder sb, ExtractedProject project)
561+
{
562+
if (!project.ReferencesReportsModule && project.Reports.Count == 0)
563+
return;
564+
565+
sb.AppendLine("## Reports");
566+
sb.AppendLine();
567+
568+
if (project.Reports.Count == 0)
569+
{
570+
sb.AppendLine("This application registers `ReportsModuleV2` and declares **no report in source**.");
571+
sb.AppendLine("That is not the same as having none: users design reports at run time and they are");
572+
sb.AppendLine("stored in the database, out of reach of anything reading this repository. The number");
573+
sb.AppendLine("is unknown, not zero — do not tell anyone this application has no reports.");
574+
sb.AppendLine();
575+
576+
return;
577+
}
578+
579+
foreach (var report in project.Reports)
580+
{
581+
var opens = report.ParametersType is { Length: > 0 } parameters
582+
? $", after a `{parameters}` dialog"
583+
: "";
584+
585+
sb.AppendLine($"- **{report.DisplayName}** — over `{report.DataType}`{opens}"
586+
+ FilterClause(report));
587+
}
588+
589+
sb.AppendLine();
590+
591+
if (project.ReferencesReportsModule)
592+
{
593+
sb.AppendLine("`ReportsModuleV2` is registered, so users can also design reports at run time.");
594+
sb.AppendLine("Those live in the database and are not listed here: **this is a lower bound**.");
595+
sb.AppendLine();
596+
}
597+
598+
if (project.UnregisteredReportLayouts.Count > 0)
599+
{
600+
sb.AppendLine($"{project.UnregisteredReportLayouts.Count} more layout"
601+
+ (project.UnregisteredReportLayouts.Count == 1 ? " is" : "s are")
602+
+ " in the repository that no registration names — usually exported from the running");
603+
sb.AppendLine("application and imported by hand. Ask `xaf_reports` for them.");
604+
sb.AppendLine();
605+
}
606+
}
607+
608+
/// <summary>The filter a report carries, which is the business decision inside it.</summary>
609+
private static string FilterClause(ExtractedReport report) =>
610+
report.Layout?.FilterString is { Length: > 0 } filter ? $", filtered to `{filter}`" : "";
611+
550612
private static void WriteMigrations(StringBuilder sb, ExtractedProject project)
551613
{
552614
if (project.Migrations.Count == 0)

0 commit comments

Comments
 (0)