Skip to content

Commit 64c3e14

Browse files
peopleworksclaude
andcommitted
Prepare the 0.10.0 release: NuGet, the MCP registry, and README figures
Three packages ship: Core (the extraction engine), Cli (the xaflogic tool) and Mcp. Version 0.9.0 was the point the repository went public and never reached a package feed, so everything since is gathered under 0.10.0. The MCP server becomes installable on its own. It was a library the CLI hosted, which meant the only way to get it was to install the whole CLI; it is now also a dotnet tool carrying PackageType=McpServer and a .mcp/server.json manifest, so `dnx XafLogicExplainer.Mcp` runs it and the MCP registry can list it. One implementation, two ways in. Module auto-discovery moved into the Mcp project, because both entry points need it and neither can ask: a marketplace plugin declares `xaflogic mcp` with no arguments, and an MCP client launching dnx is in the same position. Discovery is what makes "install it and it works" true rather than aspirational. Publishing uses NuGet Trusted Publishing -- OIDC at run time, no API key stored anywhere. The release job refuses to push when the tag disagrees with the packed version, when any package is missing its .snupkg, or when the MCP manifest's two version fields have drifted from the tag. Each of those would otherwise fail quietly: --skip-duplicate reports success on a version mismatch, absent symbols surface months later when someone tries to step into the library, and a stale manifest makes the registry advertise a version that does not exist. The tests run against the test project rather than the solution, because the solution carries the Blazor widget and its DevExpress reference cannot restore on a public runner. The site's three figures are now in the README as well. They are exported rather than copied: inline in the page they inherit its tokens and its theme switch, and a standalone file inherits nothing -- GitHub renders SVG through an <img>, which cannot see the theme of the page framing it, so a copied figure would arrive colourless and unsized. site/export-figures.py resolves the tokens for each theme and adds the namespace, and <picture> lets GitHub choose. Generated, so they cannot drift from the site. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 894c8b0 commit 64c3e14

17 files changed

Lines changed: 984 additions & 69 deletions

.github/workflows/nuget.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Publish XafLogicExplainer.Core (library), .Cli (dotnet tool) and .Mcp (dotnet tool + MCP server)
2+
# to NuGet.org.
3+
#
4+
# No API key is stored anywhere. This uses NuGet Trusted Publishing, which trades this workflow's
5+
# OIDC token for a short-lived key at run time. It needs a policy at
6+
# nuget.org -> Account -> Trusted Publishing:
7+
#
8+
# Package Owner: peopleworksservices · Repository Owner: peopleworks
9+
# Repository: XAFLogicExplainer · Workflow File: nuget.yml
10+
#
11+
# To ship a version: bump <Version> in Directory.Build.props and the two version fields in
12+
# src/XafLogicExplainer.Mcp/.mcp/server.json, merge, then publish a GitHub Release tagged
13+
# v<that version>. Tests must pass before anything is pushed.
14+
name: Publish NuGet
15+
16+
on:
17+
release:
18+
types: [published]
19+
workflow_dispatch:
20+
21+
permissions:
22+
contents: read
23+
id-token: write # required to obtain the OIDC token for trusted publishing
24+
25+
jobs:
26+
publish:
27+
# Only v<version> tags mean "publish to NuGet". Any other release tag would be read as a
28+
# package version, find nothing packed under that name, and fail — a red X on a release that
29+
# did nothing wrong.
30+
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'v')
31+
runs-on: ubuntu-latest
32+
33+
steps:
34+
- uses: actions/checkout@v4
35+
36+
- name: Setup .NET
37+
uses: actions/setup-dotnet@v4
38+
with:
39+
dotnet-version: "10.0.x"
40+
41+
# A broken build must never reach NuGet — published versions cannot be deleted.
42+
#
43+
# The test project is named rather than the solution: the solution carries the Blazor
44+
# widget, whose DevExpress package reference cannot restore on a public runner. Building
45+
# the tests pulls in Core and Mcp, which is everything being published bar the CLI.
46+
- name: Test
47+
run: dotnet test tests/XafLogicExplainer.Tests -c Release --nologo
48+
49+
- name: Pack
50+
run: |
51+
dotnet pack src/XafLogicExplainer.Core -c Release -o out
52+
dotnet pack src/XafLogicExplainer.Cli -c Release -o out
53+
dotnet pack src/XafLogicExplainer.Mcp -c Release -o out
54+
ls -1 out/
55+
56+
- name: Check the release tag matches what we built
57+
if: github.event_name == 'release'
58+
run: |
59+
want="${GITHUB_REF_NAME#v}"
60+
61+
# Guards the classic footgun: tagging v0.11.0 while Directory.Build.props still says
62+
# 0.10.0. --skip-duplicate would swallow that silently and report success.
63+
if ! ls out/*."$want".nupkg >/dev/null 2>&1; then
64+
echo "::error::Release is tagged $GITHUB_REF_NAME, but nothing packed as version $want."
65+
echo "Bump <Version> in Directory.Build.props to $want, or retag the release."
66+
ls -1 out/
67+
exit 1
68+
fi
69+
70+
# Symbols are a .NET Foundation eligibility criterion, and a gap here would only surface
71+
# months later when someone tries to step into the library. Every .nupkg must have its
72+
# .snupkg beside it — that adjacency is also what makes the push pick it up.
73+
for pkg in out/*."$want".nupkg; do
74+
if [ ! -f "${pkg%.nupkg}.snupkg" ]; then
75+
echo "::error::$pkg has no matching .snupkg — symbol publishing is broken."
76+
echo "Check IncludeSymbols/SymbolPackageFormat in Directory.Build.props."
77+
exit 1
78+
fi
79+
done
80+
81+
# The MCP manifest carries its own version twice and the MCP registry serves it to
82+
# clients. If it drifts, the registry advertises a version that does not exist.
83+
manifest="src/XafLogicExplainer.Mcp/.mcp/server.json"
84+
server_version=$(jq -r '.version' "$manifest")
85+
package_version=$(jq -r '.packages[0].version' "$manifest")
86+
if [ "$server_version" != "$want" ] || [ "$package_version" != "$want" ]; then
87+
echo "::error::$manifest says version=$server_version, packages[0].version=$package_version — expected $want."
88+
exit 1
89+
fi
90+
91+
echo "Tag $GITHUB_REF_NAME matches the packages and the MCP manifest."
92+
93+
- name: NuGet login (trusted publishing)
94+
id: login
95+
uses: NuGet/login@v1
96+
with:
97+
user: peopleworksservices
98+
99+
# The glob deliberately names only *.nupkg: the NuGet client pushes the matching .snupkg to
100+
# the symbol server on its own whenever one sits beside the package. Adding *.snupkg here
101+
# would push each symbol package twice.
102+
- name: Push to NuGet
103+
run: |
104+
dotnet nuget push "out/*.nupkg" \
105+
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
106+
--source https://api.nuget.org/v3/index.json \
107+
--skip-duplicate

CHANGELOG.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
## [0.10.0] — 2026-08-10
11+
12+
First release published to NuGet. Version 0.9.0 was the point the repository went public; nothing
13+
was ever pushed to a package feed under it, so everything since is gathered here.
14+
15+
Three packages: `XafLogicExplainer.Core` (the extraction engine),
16+
`XafLogicExplainer.Cli` (the `xaflogic` tool) and `XafLogicExplainer.Mcp`
17+
(an MCP server, installable on its own with `dnx`).
18+
19+
Still 0.x deliberately. The extraction engine is production-proven, but this release changed its
20+
behaviour in six places and has been verified against one real application. 1.0.0 is earned once
21+
the extractor has read codebases we did not write.
22+
1023
### Added
1124

1225
- **`xaflogic agents`** — writes `AGENTS.md`, `CLAUDE.md` and `.github/copilot-instructions.md`
@@ -112,11 +125,8 @@ documentation:
112125

113126
### Planned
114127

115-
- MCP server, so any agent can query an XAF codebase live
116-
- Agent skill installable from this repository, alongside DevExpress's own `dx-xaf` plugin
117128
- AI provider abstraction (OpenAI, Azure OpenAI, Anthropic, Ollama) for `--enrich`
118-
- xUnit test suite over a synthetic XAF fixture that needs no DevExpress reference
119-
- Optional DevExpress ground-truth catalog, generated locally by licensees
129+
- Splitting the 1,500-line `Program.cs` into one file per command
120130

121131
## [0.9.0] — 2026-08-10
122132

@@ -164,5 +174,6 @@ applications; this is the point where it becomes a community project.
164174
- `XafLogicExplainer.Core` references no DevExpress assemblies and needs no DevExpress license.
165175
Only the Blazor widget does.
166176

167-
[Unreleased]: https://github.com/peopleworks/XAFLogicExplainer/compare/v0.9.0...HEAD
177+
[Unreleased]: https://github.com/peopleworks/XAFLogicExplainer/compare/v0.10.0...HEAD
178+
[0.10.0]: https://github.com/peopleworks/XAFLogicExplainer/releases/tag/v0.10.0
168179
[0.9.0]: https://github.com/peopleworks/XAFLogicExplainer/releases/tag/v0.9.0

Directory.Build.props

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
-->
1111

1212
<PropertyGroup>
13-
<!-- One version for the whole repo. 0.9.x is deliberate: the extraction pipeline is
14-
proven in production, but the agent-facing surface (MCP server, AGENTS.md sink)
15-
is still landing. 1.0.0 is earned when those ship, not assumed. -->
16-
<Version>0.9.0</Version>
13+
<!-- One version for the whole repo, and the version the release tag must match.
14+
Still 0.x deliberately: the extraction engine is production-proven, but this release
15+
changed its behaviour in six places and has been verified against one real application.
16+
1.0.0 is earned once the extractor has read codebases we did not write. -->
17+
<Version>0.10.0</Version>
1718

1819
<TargetFramework>net10.0</TargetFramework>
1920
<ImplicitUsings>enable</ImplicitUsings>
@@ -37,6 +38,7 @@
3738
<PackageLicenseExpression>MIT</PackageLicenseExpression>
3839
<PackageProjectUrl>https://github.com/peopleworks/XAFLogicExplainer</PackageProjectUrl>
3940
<PackageReadmeFile>README.md</PackageReadmeFile>
41+
<PackageIcon>icon.png</PackageIcon>
4042
<PackageTags>xaf;devexpress;expressapp;roslyn;documentation;mcp;ai-agents;xpo;efcore;xafml</PackageTags>
4143
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
4244
</PropertyGroup>
@@ -66,9 +68,11 @@
6668
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
6769
</PropertyGroup>
6870

69-
<!-- The README is packed into every NuGet package, so each one needs it at its own root. -->
71+
<!-- The README and icon are packed into every NuGet package, so each one needs them at its own
72+
root. Declared once here rather than repeated in three csproj files that would drift. -->
7073
<ItemGroup Condition="'$(IsPackable)' != 'false'">
7174
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" Visible="false" />
75+
<None Include="$(MSBuildThisFileDirectory)assets\icon.png" Pack="true" PackagePath="\" Visible="false" />
7276
</ItemGroup>
7377

7478
</Project>

README.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
[![CI](https://github.com/peopleworks/XAFLogicExplainer/actions/workflows/ci.yml/badge.svg)](https://github.com/peopleworks/XAFLogicExplainer/actions/workflows/ci.yml)
44
[![License: MIT](https://img.shields.io/github/license/peopleworks/XAFLogicExplainer?color=blue)](LICENSE)
5+
[![NuGet CLI](https://img.shields.io/nuget/v/XafLogicExplainer.Cli?logo=nuget&label=CLI)](https://www.nuget.org/packages/XafLogicExplainer.Cli)
6+
[![NuGet Core](https://img.shields.io/nuget/v/XafLogicExplainer.Core?logo=nuget&label=Core)](https://www.nuget.org/packages/XafLogicExplainer.Core)
7+
[![NuGet MCP](https://img.shields.io/nuget/v/XafLogicExplainer.Mcp?logo=nuget&label=MCP%20server)](https://www.nuget.org/packages/XafLogicExplainer.Mcp)
58
[![.NET 10](https://img.shields.io/badge/.NET-10-512BD4?logo=dotnet&logoColor=white)](https://dotnet.microsoft.com/)
69
[![MCP](https://img.shields.io/badge/MCP-server-000000?logo=modelcontextprotocol&logoColor=white)](https://modelcontextprotocol.io/)
710
[![XAF](https://img.shields.io/badge/DevExpress-XAF-FF7200?logo=devexpress&logoColor=white)](https://www.devexpress.com/products/net/application_framework/)
@@ -22,6 +25,11 @@ whatever agent you code with.
2225
DevExpress has done excellent work making AI agents fluent in XAF. Two pieces already exist,
2326
and this is the third:
2427

28+
<picture>
29+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/how-it-fits-dark.svg">
30+
<img alt="Three kinds of knowledge an agent needs about an XAF codebase. Two are already solved by DevExpress tooling; the third — what your own application does — is the gap this project fills." src="docs/assets/how-it-fits-light.svg">
31+
</picture>
32+
2533
| Teaches the agent… | Tool |
2634
| --- | --- |
2735
| How XAF works in general | [DevExpress `agent-skills`](https://github.com/DevExpress/agent-skills) |
@@ -40,6 +48,11 @@ for the official reference, and use this for your own codebase. None of them rep
4048

4149
## What it extracts
4250

51+
<picture>
52+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/extraction-pipeline-dark.svg">
53+
<img alt="Source files are parsed as syntax by Roslyn, never compiled, producing a model rendered to agent files, an MCP server, or Markdown and JSON." src="docs/assets/extraction-pipeline-light.svg">
54+
</picture>
55+
4356
Everything below is read as **syntax**, using Roslyn. Your project never has to compile, and this
4457
tool never links against DevExpress assemblies:
4558

@@ -71,6 +84,12 @@ No account, no API key, no server. Your agent understands the application on its
7184
forever. Dumping 70 KB of entity detail there would crowd out the actual question. So the output is
7285
tiered:
7386

87+
<picture>
88+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/two-tier-context-dark.svg">
89+
<img alt="What a full documentation dump costs an agent's context on every request, against the tiered output that leaves that room free." src="docs/assets/two-tier-context-light.svg">
90+
</picture>
91+
92+
7493
| | | |
7594
| --- | --- | --- |
7695
| `AGENTS.md` | ~11 KB | Always loaded: ground rules, complete inventories, conventions, recipes |
@@ -94,17 +113,24 @@ application while you work on it, and cannot go stale.
94113
/plugin install xaf-logic-explainer@peopleworks-xaf
95114
```
96115

97-
That installs a skill and an MCP server in one step. For any other MCP client, add:
116+
That installs a skill and an MCP server in one step. For any other MCP client, either run it
117+
straight from NuGet with no install:
98118

99119
```json
100120
{
101121
"mcpServers": {
102-
"xaf": { "command": "xaflogic", "args": ["mcp"] }
122+
"xaf": { "command": "dnx", "args": ["XafLogicExplainer.Mcp", "--yes"] }
103123
}
104124
}
105125
```
106126

107-
Started from a solution directory it finds the XAF module by itself.
127+
…or point at the CLI if you already have it:
128+
129+
```json
130+
{ "mcpServers": { "xaf": { "command": "xaflogic", "args": ["mcp"] } } }
131+
```
132+
133+
Started from a solution directory it finds the XAF module by itself, so neither form needs a path.
108134

109135
| Tool | Answers |
110136
| --- | --- |

assets/icon.png

3.52 KB
Loading
Lines changed: 85 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)