iru-setup-windows-app
Scaffold a new Windows desktop app on .NET 10 / C# 14 at the repository root — `global.json` (SDK pinned, `rollForward: latestFeature`), `Directory.Build.props` (the app `<Version>` `x.y.z[-dev]`, nul
SKILL.md.claude/skills/iru-setup-windows-app/SKILL.md
GitHubSetup Windows App
Generate a complete, buildable Windows desktop app on .NET 10 — a solution with a UI-free Core library holding all
testable logic, a WPF, WinUI 3 or Windows Forms app project composed with the Generic Host, an MSTest 4 Core test
project at 100% line coverage, optional FlaUI UI tests, DocFX API docs and MSIX packaging — from the templates in this
skill's reference/ files. Every template was rendered into throwaway projects and proven against the real toolchain
on 2026-10-16 (.NET SDK 10.0.402 on macOS arm64, DocFX 2.81.0, PowerShell 7.6.7) for ui: wpf (mvvm), ui: winforms
(mvc and mvvm) and ui: winui (mvvm); everything that needs Windows is listed as unverified in Known quirks /
verification notes.
The layout is the scaffold contract the Windows coding and gate skills (iru-windows-code-one-task(-group),
iru-dotnet-test, iru-dotnet-coverage, iru-dotnet-code-quality, iru-dotnet-docfx, iru-dotnet-bump-version)
and iru-setup-windows-github-workflows detect and call — Step 8's contract table lists the confirmed commands and
report paths. This skill writes no CI workflow, .gitignore, README or changelog (iru-setup-windows-repository
chains the skills that do) and no Sonar file: SonarScanner for .NET takes the project key and organization on its
command line, so iru-setup-windows-github-workflows owns them.
Sources: the facts behind every template are the official Microsoft Learn pages — WPF
(https://learn.microsoft.com/en-us/dotnet/desktop/wpf/, incl. app-development/how-to-use-host-builder), Windows
Forms (https://learn.microsoft.com/en-us/dotnet/desktop/winforms/, incl. high-dpi-support-in-windows-forms and
whats-new/net100), WinUI / Windows App SDK (https://learn.microsoft.com/en-us/windows/apps/winui/winui3/,
…/windows-app-sdk/single-project-msix, …/develop/platform/xaml/x-bind-markup-extension,
…/package-and-deploy/ci-for-winui3), the MVVM Toolkit
(https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/, generators/observableproperty,
generators/relaycommand, generators/errors/mvvmtk0042), the Generic Host
(https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host), .NET publishing
(https://learn.microsoft.com/en-us/dotnet/core/deploying/, …/trimming/incompatibilities), MSIX
(https://learn.microsoft.com/en-us/windows/msix/overview, …/package/sign-app-package-using-signtool) and MSTest
(https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-intro) — plus irurueta docs:
programming-languages/csharp/build-and-tooling.adoc, coding-conventions-and-documentation.adoc and testing.adoc
(https://github.com/albertoirurueta/docs; its planned Windows Apps section is issue #191). Gaps stated honestly: there
is no Microsoft Learn MVVM article specific to WPF, no official dialog/navigation service, no official desktop UI-test
recommendation (FlaUI is third-party; WinAppDriver has had no release since 2020), and trimming/Native AOT are
unsupported for WPF/WinForms.
Reference files — read only what this run needs
| Read | When |
|---|---|
reference/common-templates.md |
Always. global.json, Directory.Build.props, Directory.Packages.props, .editorconfig, stylecop.json, <AppName>.slnx, the Core project and sample code (per architecture), app.manifest, the Core test project, tests/coverlet.runsettings, docfx.json + docfx/. |
reference/wpf-templates.md |
ui: wpf only. |
reference/winui-templates.md |
ui: winui only. |
reference/winforms-templates.md |
ui: winforms only (both architectures). |
reference/packaging-templates.md |
Always — the generate-assets.py helper (app icon for every ui, WinUI tiles), plus packaging/ for ui: wpf|winforms with msix: yes. |
reference/uitest-templates.md |
ui-tests: flaui only — the FlaUI project plus the UI-contract helpers (UiQuality, UiScreenshots, AccessibilityCheck) and the matrix capture test. |
Conditional-marker convention used in every reference file: a line // >>> <condition> (or # >>> <condition>,
or <!-- >>> <condition> --> in XML/XAML) opens a block that is written only when the condition holds, and the
matching <<< line closes it; the marker lines themselves are never written. Conditions: ui: <v> and
architecture: <v> (| separates alternatives, e.g. ui: wpf|winforms), msix (= msix: yes) and flaui
(= ui-tests: flaui); comma-separated conditions must all hold. A file heading suffixed (condition) exists only
when it holds. Placeholders are written __Token__ (not <token>, which collides with C# generics and XML) and
come from Step 5's table; a heading's path uses them too (src/__AppName__.Core/…).
Architecture matrix — which ui × architecture combinations are valid
ui |
architecture |
Why |
|---|---|---|
wpf |
mvvm only |
XAML's data binding + ICommand is the pattern WPF is designed around; MVC has no XAML idiom (no controller concept, the view is bound declaratively). Microsoft's guidance for XAML apps is MVVM. |
winui |
mvvm only |
Same reason; WinUI's own "Data binding and MVVM" guidance and tutorial use the MVVM Toolkit, and x:Bind binds to a view-model property on the window/page. |
winforms |
mvc (Recommended) or mvvm |
The designer produces event-driven forms that map naturally onto a controller handling view events through a passive-view interface (IMainView in Core, so the controller is unit-tested with a fake view). mvvm is viable since .NET 8 (BindingSource + Button.Command binding to MVVM Toolkit view models). |
The architecture is recorded by the directory layout the coding skills detect: src/<AppName>.Core/ViewModels/
→ MVVM; src/<AppName>.Core/Controllers/ + src/<AppName>.Core/Views/I<X>View.cs (+ Models/) → MVC.
Step 0 — Resolve inputs
Invoked stand-alone (/iru-setup-windows-app) or by iru-setup-windows-repository / the iru-setup-repository
front door, which pass resolved answers as key: value lines, e.g.:
ui: wpf
architecture: mvvm
app-name: Bookshelf
display-name: Bookshelf Desktop
publisher: CN=Jane Doe
publisher-display-name: Jane Doe
package-name: JaneDoe.Bookshelf
branching: gitflow
license: MIT License
developer-name: Jane Doe
developer-email: jane@example.com
organization-url: https://github.com/example-org
open-source: yes
visibility: public
ci: yes
sonar: cloud
distribution: store
mode: new
on-existing: ask
Recognized keys: ui (wpf/winui/winforms), architecture (mvvm/mvc — validated against the matrix; an
invalid combination from args is reported and re-asked, never silently changed), app-name (PascalCase C#
identifier ^[A-Z][A-Za-z0-9]*$ — the solution, project, assembly and exe name), display-name (window title, MSIX
DisplayName; default app-name), root-namespace (default app-name; Core uses <root-namespace>.Core),
publisher (MSIX Identity/@Publisher, an X.500 subject CN=… that must equal the signing certificate's subject, or
the Partner Center value for the Store; default CN=<developer-name>), publisher-display-name (default
developer-name), package-name (MSIX Identity/@Name, 3–50 chars of [A-Za-z0-9.-]; default
<Owner>.<app-name> from Step 3, non-identifier characters dropped), version (default 1.0.0-dev for branching: gitflow, 1.0.0 for main-only), rids (default win-x64;win-arm64 — WinUI RuntimeIdentifiers/Platforms and
the packaging script's default), msix (yes/no; ui: winui is always packaged → yes; WPF/WinForms default
yes when distribution ≠ none, else no), ui-tests (flaui/none, default flaui), branching
(gitflow/main-only, default gitflow), accessibility (enforce/report/off/none, default enforce — the
automated accessibility level written to .ui-quality.yml: enforce makes Axe.Windows violations fail the UI tests,
report lists them, off skips the scan), license, developer-name, developer-email, organization-url,
open-source (yes/no), visibility, ci, sonar (+ sonar-organization/sonar-project-key/sonar-host-url
— recorded for the report only: no file carries them), distribution (none/internal/store — only keys the
msix default), mode (new/existing), on-existing (ask/keep/update/replace, default ask). Every key
found is resolved — skip its question in Step 2. Unknown keys from a full shared-args set (ci-tests, pages,
signing, winget, integration-branch, stable-branch, publish, …) are ignored and named in the report.
mode: existing never means "gap-fill silently": Step 1 still warns about, and asks about, every pre-existing file
group. Only on-existing skips that question (keep = create only missing files; update/replace applied to
every existing group; the warning is still printed). Orchestrators forward on-existing only when their own args
received it.
Accessibility level none and assistive-technology templates. accessibility takes enforce, report, off or
none (UI contract §8; the stand-alone question gains a fourth option, None — opt out entirely, e.g. a mandated
non-compliant design). With none: write accessibility: none to .ui-quality.yml, do not install or configure the
accessibility test tooling (Axe.Windows), and omit the accessibility-specific attributes from the generated templates.
With enforce, report or off: include the assistive-technology attributes from the contract's §8 platform
subsections in every generated template — AutomationProperties.Name/HelpText on icon buttons and images, keyboard access and tab order for Narrator, system font scaling and high-contrast resources — and, for enforce/report only, the automated checks. In
mode: existing, a prior .ui-quality.yml follows this skill's existing Update / Keep / Replace prompt and its
accessibility value is never silently flipped (Update sets it only when args supplied it).
Step 1 — Survey the repository
Check for every file Step 6 would write: global.json, Directory.Build.props, Directory.Packages.props,
.editorconfig, stylecop.json, .ui-quality.yml, any *.slnx/*.sln, src/*/*.csproj, src/** sources, tests/*/*.csproj,
tests/**, tests/coverlet.runsettings, docfx.json, docfx/, packaging/, and any *.wapproj. Then tell the user
which of the two this run is doing:
Scaffolding from scratch — none exist: go to Step 2.
Updating an existing Windows app — any exists in this skill's layout (
src/<AppName>.Core+src/<AppName>): warn, listing every file found by path, that each existing file group below will be decided individually (missing files are still created). Groups (only those with a file present):global.json,Directory.Build.props,Directory.Packages.props, quality config (.editorconfig,stylecop.json), the solution file, project files (every*.csproj),tests/coverlet.runsettings, UI quality config (.ui-quality.yml), DocFX config (docfx.json,docfx/), packaging (packaging/,Package.appxmanifest, publish profiles), and the source skeleton (every*.cs,*.xaml,app.manifest,Assets/). Unlesson-existingdecides, ask withAskUserQuestion, one question per group (batch up to 4 per call), naming each concrete conflict with this run's choices (e.g. "src/Bookshelf.CorehasControllers/but you chosearchitecture: mvvm", "Directory.Build.propshas no<Version>— each csproj declares its own", "Directory.Packages.propsis missing — projects pin versions inline", "the app project setsUseWPFbut you choseui: winforms"):- Update (Recommended for build files, quality config, solution, run settings and DocFX config) — "Merge: add
what's missing and correct outdated parts using this skill's template as reference — analyzer/warnings-as-errors
properties, central package versions bumped to Step 4's lookup,
EnableWindowsTargeting, the SARIFErrorLog, the runsettings formats/exclusions, the missing keys of.ui-quality.yml(its existingdirections/themesandaccessibilityare kept unlessaccessibilitycame fromargs, which then wins and is reported); keep every project, package, property and setting this skill doesn't own. You review a diff; customizations survive, but a customized part this skill owns may be rewritten." For the source skeleton, Update only creates its missing files. - Keep as is (Recommended for the source skeleton and packaging) — "Leave the file(s) untouched. Nothing you customized changes, but they may stay out of date or disagree with this run's choices — the report lists each mismatch."
- Replace — "Regenerate from this skill's template. Matches the house pattern exactly, but every customization
is lost (recoverable only from git history)." Never the default. Even on Replace, never overwrite a
*.cs/*.xamlfile unless its content exactly matches this skill's original template (i.e. it is still the untouched sample). - Stop (first question only) — "End this skill now without changing any file." Report and end.
Ask about groups whose expected content depends on Step 2 answers (
ui,architecture,msix,ui-tests) after Step 2 whenargsdidn't already resolve them. When updating, read the current app name (theWinExeproject'sAssemblyName/file name), root namespace,ui(UseWPF/UseWinUI/UseWindowsForms), architecture (Core folders), version and the MSIX identity first and offer them as Step 2's defaults.- Update (Recommended for build files, quality config, solution, run settings and DocFX config) — "Merge: add
what's missing and correct outdated parts using this skill's template as reference — analyzer/warnings-as-errors
properties, central package versions bumped to Step 4's lookup,
A foreign layout — a single-project app (
dotnet new wpf/winforms/winuioutput with the logic in the app project and no*.Core), a.slnholding other projects, a Windows Application Packaging Project (*.wapproj), or a .NET Framework (non-SDK-style) project: this is not this skill's scaffold. Warn explicitly and ask (AskUserQuestion) whether to add this skill's missing pieces alongside without touching the existing projects (e.g. onlyglobal.json, the quality config, central package management, the runsettings and DocFX config — each still per-group Update/Keep/Replace; extracting logic into aCoreproject is a coding task, never done here), or stop. Never move, rename or convert an existing project, and never migrate a.slnto.slnxwithout an explicit yes.
Step 2 — Collect the required inputs
For anything Step 0 didn't resolve, ask directly (plain conversation, pre-filled from Step 1 when updating):
app-name, display-name, root-namespace, publisher / publisher-display-name / package-name
(only when an MSIX is produced — say the Store values come from the Partner Center reservation and a self-signed or
purchased certificate's subject must equal publisher), and developer name/email/organization URL.
Then, with AskUserQuestion (skip any resolved):
- ui — WPF (Recommended for most line-of-business apps) (
wpf: mature, richest XAML/binding model, Fluent theme since .NET 9); WinUI 3 (winui: Microsoft's recommended native UI for new apps, Windows App SDK 2.x, always MSIX-packaged here; builds and runs only on Windows); Windows Forms (winforms: designer-driven, fastest for simple forms-over-data tools). - architecture — only when
ui: winforms(WPF/WinUI: state thatmvvmis the only valid choice and why, per the matrix): MVC with a passive view (Recommended) / MVVM with BindingSource + commands. - msix — only for WPF/WinForms: Yes (recommended when
distribution≠none: signed MSIX for GitHub Releases/App Installer or the Store) / No (zip/xcopy distribution only). - ui-tests — FlaUI UI tests (Recommended) — say plainly it is third-party, runs only on Windows with an interactive desktop, and that Microsoft has no current desktop UI-test recommendation — / None.
- accessibility — only when
ui-tests: flauiand stand-alone (an orchestrator resolves it; otherwise defaultenforce): "Support accessibility and assistive technologies?" — Enforce (Recommended) (Axe.Windows violations fail the UI tests) / Report only (listed in.ui-screenshots/a11y-report.txt, never failing) / Off (not run; the coding defaults still apply). - license — MIT License (recommended for an app) / Apache License 2.0 / No license (proprietary) / Other. If one is
chosen, remind the user in Step 8 to add a root
LICENSE(iru-check-licensecan backfill headers). - open-source, sonar and distribution — asked stand-alone only (an orchestrator resolves them): ask
open-sourcefirst; recommendsonar: cloudonly when open source and public (SonarCloud is free only for public/open-source projects), otherwisenone(orself-hosted), and say that this skill writes no Sonar file —iru-setup-windows-github-workflowspasses the key/organization to SonarScanner for .NET.distributiondefault:storewhen open source, otherwise recommendinternal; it only decides themsixdefault here.
Step 3 — Infer repository information
Parse git remote get-url origin (git@host:owner/repo.git or https://host/owner/repo.git) for <Owner> (the
package-name default, PascalCased, non-identifier characters dropped). No origin → ask.
Step 4 — Look up toolchain versions
Look every version up at run time; fall back to this table (looked up and verified working together on
2026-10-16) only when a lookup fails, and say so in Step 8. NuGet flat-container lookups:
curl -fsSL https://api.nuget.org/v3-flatcontainer/<id-lowercase>/index.json → the highest version without a -
suffix (the list is SemVer-ordered), except where the table says otherwise. After a newer combination is adopted, run
Step 7 before trusting it.
| Component | Lookup | 2026-10-16 fallback |
|---|---|---|
.NET SDK (global.json) |
https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/10.0/releases.json → latest-sdk (stay on the 10.0 LTS channel; .NET 11 is not the default until it is GA and verified) |
10.0.402 (runtime 10.0.13) |
CommunityToolkit.Mvvm (architecture: mvvm) |
flat container; ≥ 8.4.1 needed for partial-property [ObservableProperty] without LangVersion preview |
8.4.2 |
Microsoft.Extensions.Hosting |
flat container, highest 10.0.x (match the SDK's major) | 10.0.13 |
Microsoft.WindowsAppSDK (ui: winui) |
flat container, highest stable (skip -experimental/-preview) |
2.5.1 |
Microsoft.Windows.SDK.BuildTools (ui: winui) |
flat container, highest stable | 10.0.28000.2705 |
MSTest (metapackage) |
flat container | 4.5.1 (brings Microsoft.NET.Test.Sdk 18.10.1, MSTest analyzers) |
coverlet.collector |
flat container | 10.1.0 |
FlaUI.UIA3 (ui-tests: flaui) |
flat container | 5.0.0 |
Axe.Windows (ui-tests: flaui) |
flat container (Microsoft's automated accessibility rules for Windows UI; netstandard2.0) |
2.4.2 |
StyleCop.Analyzers |
not "highest stable": 1.1.118 (2019) predates modern C# syntax. Take the newest 1.2.0-beta.*; adopt a newer prerelease line (e.g. 1.3.x-beta) only when its first release is at least two weeks old and Step 7 passes with it (https://api.nuget.org/v3/registration5-semver1/stylecop.analyzers/index.json has publish dates) |
1.2.0-beta.556 (the 1.3.x-beta dailies first appeared 2026-10-12) |
| DocFX (verification tool, never added to the repo) | flat container docfx |
2.81.0 |
Step 5 — Reference templates
Read the reference files this run needs (table above). Placeholders:
| Placeholder | Source |
|---|---|
__AppName__ / __RootNamespace__ / __DisplayName__ |
Step 0/2 |
__Version__ |
Step 0 (1.0.0-dev gitflow / 1.0.0 main-only) |
__MsixVersion__ |
__Version__ without any pre-release/build suffix, plus .0 (1.0.0-dev → 1.0.0.0; the Store requires revision 0) |
__Publisher__ / __PublisherDisplayName__ / __PackageName__ |
Step 0/2/3 (XML-escape &, <, ") |
__Company__ |
developer-name (else publisher-display-name) |
__Year__ |
current year |
__Rids__ |
rids (win-x64;win-arm64) |
__DotnetSdkVersion__ and every __…Version__ package token |
Step 4 |
__Accessibility__ |
Step 0/2 accessibility (default enforce) |
Directory layout produced ([ui]/[arch] mark variant-only files):
global.json Directory.Build.props Directory.Packages.props .editorconfig stylecop.json .ui-quality.yml <AppName>.slnx
docfx.json docfx/index.md docfx/toc.yml
src/<AppName>.Core/<AppName>.Core.csproj
src/<AppName>.Core/Services/{IGreetingService,GreetingService}.cs
src/<AppName>.Core/ViewModels/MainViewModel.cs [mvvm]
src/<AppName>.Core/{Models/GreetingModel,Views/IMainView,Controllers/MainController}.cs [mvc]
src/<AppName>/<AppName>.csproj src/<AppName>/app.manifest src/<AppName>/UiTestOptions.cs src/<AppName>/Assets/AppIcon.ico
src/<AppName>/{App.xaml,App.xaml.cs,MainWindow.xaml,MainWindow.xaml.cs,AssemblyInfo.cs} [wpf]
src/<AppName>/{App.xaml,App.xaml.cs,MainWindow.xaml,MainWindow.xaml.cs,Package.appxmanifest} [winui]
src/<AppName>/Assets/*.scale-200.png … src/<AppName>/Properties/{launchSettings.json,PublishProfiles/win-{x64,arm64}.pubxml} [winui]
src/<AppName>/{Program.cs,MainForm.cs,MainForm.Designer.cs} [winforms]
packaging/{AppxManifest.xml,build-msix.ps1,Assets/*.png} [wpf|winforms + msix]
tests/<AppName>.Core.Tests/{<AppName>.Core.Tests.csproj,MSTestSettings.cs,Fakes/,Services/,ViewModels/|Models/+Controllers/}
tests/<AppName>.UITests/{<AppName>.UITests.csproj,MSTestSettings.cs,AppLauncher.cs,UiQuality.cs,UiScreenshots.cs,AccessibilityCheck.cs,MainWindowTests.cs} [flaui]
tests/coverlet.runsettings
Step 6 — Scaffold the project
Apply Step 1's per-group decision to every file that already exists (Keep as is → skip; Update → merge after showing
git diff --no-index <current> <draft>; Replace → overwrite within Step 1's source-file limit). Files that didn't
exist are always written.
- Write every template from the reference files this run reads, applying the markers and placeholders (only the
ui-matching reference file; variant-only headings only when their condition holds). Keep UTF-8 without BOM. - Extract
generate-assets.pyfromreference/packaging-templates.mdinto a scratch directory outside the repository and runpython3 -I <scratch>/generate-assets.py <repo-root> <AppName> <ui> <msix>— it writessrc/<AppName>/Assets/AppIcon.ico(everyui), the WinUI tiles (ui: winui) orpackaging/Assets/*.png(ui: wpf|winforms,msix: yes). Never write the helper into the repository. Existing images are part of the source skeleton group — never overwritten unless Replace was chosen for it. - If
dotnetis installed, confirm the solution parses:dotnet sln <AppName>.slnx list.
Step 7 — Verify
Run through the iru-gate-runner agent (never inline — the logs are long). Warn first: the first restore downloads
every package (WinUI: the Windows App SDK, several hundred MB) and build-msix.ps1 downloads the Windows runtime
packs. On any host, with the SDK from global.json:
dotnet restore <AppName>.slnx.dotnet build <AppName>.slnx -c Release— warnings are errors, so this is also the analyzer gate; then read the per-project SARIF logs (src/*/obj/*.sarif,tests/*/obj/*.sarif) and report the count of results withoutsuppressions(the MVVM Toolkit's generated code carries suppressed CS0618/CS8625 entries — not findings).ui: winuioff Windows: building the solution fails (Known quirks) — buildsrc/<AppName>.Core,tests/<AppName>.Core.Testsand (flaui)tests/<AppName>.UITestsindividually and report the app project as "unverified (needs Windows)".dotnet format <AppName>.slnx --verify-no-changes --no-restore(WinUI off Windows: the Core test project) — the only check that surfaces IDE naming rules (IDE1006).dotnet test tests/<AppName>.Core.Tests -c Release --settings tests/coverlet.runsettings --collect "XPlat Code Coverage" --results-directory TestResults→ readline-rateofTestResults/<guid>/coverage.cobertura.xml(expected1— 100% of Core for every variant).- DocFX:
docfx docfx.json(adotnet tool install --tool-path <scratch>/docfx docfxcopy ifdocfxisn't installed — never-g; whendotnetis not onPATH, setDOTNET_ROOT) →build/docfx/index.htmlandbuild/docfx/api/, 0 warnings. - Windows only (otherwise report each as "unverified (needs Windows)", never "passed"): launch the app
(
dotnet run --project src/<AppName>for WPF/WinForms; WinUI through Visual Studio's package deploy or the(Package)launch profile);ui: winuidotnet build src/<AppName> -c Release -p:Platform=x64; withmsix: yespwsh packaging/build-msix.ps1 -RuntimeIdentifier win-x64(unsigned unless a test PFX is passed); withui-tests: flauidotnet test tests/<AppName>.UITests -c Release --filter "TestCategory=UI"(expect.ui-screenshots/screen/<MainWindow|MainForm>/{default,typical-data}--{ltr,rtl}--{light,dark}.png— 8 captures — and no Axe.Windows violation underenforce) behind the repository-wide.integration-test.lock(atomicmkdir,ownerfile, stale after 60 min, released on every exit path); for WinUI first build a self-signed MSIX,Add-AppxPackageit and exportAPP_AUMIDfromGet-AppxPackageexactly asiru-dotnet-test'suistep 2 does (mirroring the CIbuild.ymlUI step — itself unverified locally, needs Windows). Off Windows,pwsh packaging/build-msix.ps1 -LayoutOnlystill verifies the publish + layout half when PowerShell 7 is installed (never install it yourself).
Report pass/fail per command; quote error output verbatim for a failure only.
Step 8 — Report
Summarize: resolved ui, architecture, app-name, display-name, root-namespace, version and MSIX identity
(package-name, publisher, __MsixVersion__), msix, ui-tests, accessibility, rids, license (or none), and whether the run
scaffolded from scratch, updated an existing project, or stopped. Then, per Step 1 group, the action taken —
created / updated / replaced / kept as is (including .ui-quality.yml) — and whether it came from the user
or on-existing, listing each
mismatch of a kept group and, for a replaced one, what customizations could have been lost (check git diff).
State what was included versus omitted and why: the architecture choice (and, for WPF/WinUI, that MVC is not
offered); msix (packaging/ written or not; WinUI always packaged); ui-tests; sonar (no file — the keys go to
iru-setup-windows-github-workflows); distribution (only drove the msix default); which Step 4 versions were live
versus fallback; and any ignored args keys.
Scaffold contract (confirmed by running it — downstream skills depend on these)
| What | Exact name / path |
|---|---|
| Solution | <AppName>.slnx (XML solution; dotnet sln <AppName>.slnx list works) — folders /src/, /tests/, /Solution Items/ |
| Projects | src/<AppName>.Core/<AppName>.Core.csproj (net10.0, assembly <AppName>.Core, IsAotCompatible); src/<AppName>/<AppName>.csproj (WinExe, assembly <AppName>; net10.0-windows for wpf/winforms, net10.0-windows10.0.26100.0 + TargetPlatformMinVersion 10.0.17763.0 + Platforms x64;ARM64 for winui); tests/<AppName>.Core.Tests (net10.0); tests/<AppName>.UITests (net10.0-windows, IsTestProject=false off Windows) |
| UI detection | app csproj UseWPF / UseWinUI (+ EnableMsixTooling) / UseWindowsForms |
| Architecture detection | src/<AppName>.Core/ViewModels/ → mvvm; src/<AppName>.Core/Controllers/ + Views/I<X>View.cs (+ Models/) → mvc |
| Build | dotnet build <AppName>.slnx -c Release — any host for wpf/winforms (EnableWindowsTargeting is in Directory.Build.props); winui only on Windows (`-p:Platform=x64 |
| Unit tests | dotnet test tests/<AppName>.Core.Tests -c Release (VSTest runner; MSTest 4). dotnet test <AppName>.slnx also works on Linux/macOS for wpf/winforms (the UI-test project is not a test project there) |
| Coverage | dotnet test tests/<AppName>.Core.Tests -c Release --settings tests/coverlet.runsettings --collect "XPlat Code Coverage" --results-directory TestResults → TestResults/<guid>/coverage.cobertura.xml + coverage.opencover.xml (Sonar reads opencover); the <AppName> UI assembly is excluded ("not measured"); 100% line + branch for every variant |
| UI tests | dotnet test tests/<AppName>.UITests -c Release --filter "TestCategory=UI" — Windows + interactive desktop only, behind .integration-test.lock; app from APP_AUMID (WinUI: required), APP_EXE, or the newest src/<AppName>/bin/**/<AppName>.exe; automation ids nameTextBox, greetButton, messageLabel; ordinary runs exclude them with --filter "TestCategory!=UI" |
| UI contract | .ui-quality.yml at the root (accessibility, directions: [ltr, rtl], themes: [light, dark]); app launch arguments --ui-direction=ltr|rtl --ui-theme=light|dark (src/<AppName>/UiTestOptions.cs); captures <UI_SCREENSHOTS_DIR or .ui-screenshots>/screen/<MainWindow|MainForm>/<state>--<dir>--<theme>.png via tests/<AppName>.UITests/UiScreenshots.cs; Axe.Windows via AccessibilityCheck.cs (enforce fails with [a11y] …, report appends to .ui-screenshots/a11y-report.txt) |
| Analyzer SARIF | one SARIF 2.1 log per project at <project dir>/obj/<ProjectName>.sarif (e.g. src/<AppName>.Core/obj/<AppName>.Core.sarif), written by every build via Directory.Build.props ErrorLog; a command-line -p:ErrorLog=… overrides it; count only results without suppressions |
| Format check | dotnet format <AppName>.slnx --verify-no-changes --no-restore |
| DocFX | docfx docfx.json (repo root) → metadata docfx/api/*.yml (generated — gitignore it) → site build/docfx/ (index.html, api/<Namespace>.<Type>.html); Core API only |
| Version | Directory.Build.props <Version> (x.y.z-dev on gitflow develop, x.y.z otherwise); MSIX Identity/@Version x.y.z.0 in src/<AppName>/Package.appxmanifest (winui) and packaging/AppxManifest.xml (wpf/winforms + msix) — build-msix.ps1 rewrites the latter's version from Directory.Build.props at pack time anyway |
| MSIX (wpf/winforms) | pwsh packaging/build-msix.ps1 [-RuntimeIdentifier win-x64,win-arm64] [-Version x.y.z] [-CertificatePath f.pfx -CertificatePassword …] [-LayoutOnly] → publish artifacts/publish/<rid>/, layout artifacts/msix-layout/<rid>/, package artifacts/msix/<AppName>_<x.y.z.0>_<x64|arm64>.msix (needs makeappx.exe/signtool.exe from the Windows SDK) |
| MSIX (winui) | msbuild src/<AppName>/<AppName>.csproj /restore -p:Configuration=Release -p:Platform=<x64|ARM64> -p:GenerateAppxPackageOnBuild=true -p:AppxBundle=Never -p:UapAppxPackageBuildMode=SideloadOnly (per Microsoft's CI-for-WinUI page; Windows only, unverified locally) |
| Zip publish (wpf/winforms) | dotnet publish src/<AppName> -c Release -r <rid> --self-contained -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o <dir> → <AppName>.exe (+ .pdb/.xml) only; never PublishTrimmed/PublishAot (NETSDK1168 WPF, NETSDK1175 WinForms) |
Known quirks / verification notes
- Verified for real on 2026-10-16 in
$TMPDIR/iru-verify/windows/iru-setup-windows-app/{wpf,winforms-mvc, winforms-mvvm,winui}(outside this repository; .NET SDK 10.0.402 downloaded to a temp directory, macOS 27 arm64), all withmsix: yesandui-tests: flaui, plus awpf-minimalrender withmsix: no/ui-tests: none: restore;dotnet build <AppName>.slnx -c Release(WPF incl. XAML markup compile, WinForms incl. the designer file and data bindings, the FlaUI project) with 0 unsuppressed SARIF results in every project;dotnet format --verify-no-changesclean; Core tests 9 passed (mvvm) / 13 passed (mvc) with cobertura line-rate 1, branch-rate 1 (9/9 and 31/31 lines); DocFX 2.81.0 0 warnings / 0 errors;pwsh packaging/build-msix.ps1 -LayoutOnly(PowerShell 7.6.7 downloaded to a temp directory) publishedwin-x64andwin-arm64self-contained and wrote the layout withVersion="1.0.0.0"/ProcessorArchitecture="arm64"(and-Version 2.3.4-beta→2.3.4.0); single-file publish of WPF and WinForms produced only<AppName>.exe. - WinUI build fails off Windows, exactly: restore succeeds (Windows App SDK 2.5.1 graph,
RuntimeIdentifierwin-arm64from the host architecture), then the XAML compiler step runs…/microsoft.windowsappsdk.winui/2.3.9/tools/net472/XamlCompiler.exe: cannot execute binary fileand fails witherror : XamlCompiler output file "obj/Release/net10.0-windows10.0.26100.0/win-arm64/output.json" was not created. The XAML compiler may have crashed.A verification-only probe (XAML files removed,InitializeComponent/Mainstubbed, outside the repository) compiledApp.xaml.csandMainWindow.xaml.csagainst the WinUI 2.x reference assemblies with 0 analyzer results, then stopped atMakePri.exe … Exec format error. So the WinUI C# is compile-checked; the XAML (x:Bind), PRI/MSIX generation and running are unverified locally (needs Windows). - The FlaUI project cannot run off Windows: its test host needs the
Microsoft.WindowsDesktop.Appruntime ("You must install or update .NET to run this application … Framework: 'Microsoft.WindowsDesktop.App'"), which madedotnet test <AppName>.slnxexit 1 even with--filter "TestCategory!=UI". HenceIsTestProject=falseoff Windows in its csproj; it still compiles everywhere. [OSCondition(OperatingSystems.Windows)]instead ofOperatingSystem.IsWindows()+Assert.Inconclusive: MSTest 4's analyzer flags the runtime check (MSTEST0061);FlaUI.Core.Tools.OperatingSystemalso makes a bareOperatingSystemambiguous (CS0104).- Coverage stays on VSTest: the MSTest metapackage brings
Microsoft.NET.Test.Sdk, sodotnet test --collect "XPlat Code Coverage"+ coverlet works. Don't switchglobal.jsonto the Microsoft.Testing.Platform runner without re-verifying coverage — coverlet's collector is a VSTest data collector. - Analyzer fixes baked into the templates: CA1861 (constant arrays in
CollectionAssert.AreEqual) →Assert.ContainsSingle; MSTEST0068 →Assert.Contains; SA1636 fired on a plain leading//comment, so the whole file-header family SA1633–SA1641 is off (headers belong toiru-check-license); SA1210 forces alphabetical usings across themvc/mvvmmarker blocks; SA1101/SA1309 are off for the_camelCasefield convention;dotnet format(not the build) reported IDE1006 for aprivate static readonlyfield → a PascalCase rule for static readonly fields. The MVVM Toolkit's generated code produces CS0618/CS8625 entries that are suppressed in the SARIF — gates must ignore suppressed results. - NuGet audit is part of the build: with
TreatWarningsAsErrors, a newly disclosed vulnerable package (NU1901– NU1904) fails restore — intended; update the package inDirectory.Packages.props. - WinForms:
*.Designer.csis generated code to Roslyn (no StyleCop/nullable diagnostics —components = nullis fine); DPI comes fromApplicationHighDpiModein the csproj, notapp.manifest(WFAC010);Button.Commandbinding andApplication.SetColorModecompiled with no experimental diagnostic on .NET 10. ControlNames double as UI Automation ids. - WPF:
ThemeMode="System"inApp.xamlcompiles without WPF0001 (settingThemeModefrom code is the experimental API).Appbuilds the host in its constructor;OnExitstops it synchronously because an awaited continuation would never run after the dispatcher shuts down. - Trimming/AOT:
PublishTrimmed=truefails with NETSDK1168 (WPF) / NETSDK1175 (WinForms) — verified. WinUI supports trimming/AOT, but the scaffold leaves it off (Microsoft's template turnsPublishTrimmedon in Release; trim warnings would break the warnings-as-errors build — opt in deliberately). - WinUI choices: TFM
net10.0-windows10.0.26100.0and the project shape follow Microsoft's current WinUI CLI template (Microsoft.WindowsAppSDK.WinUI.CSharp.Templates,winui.mvvmapp, still alpha on NuGet); x86, the phone identity, thesystemAIModelscapability and theMicrosoft.Windows.SDK.BuildTools.WinAppdotnet runhelper are left out. - UI contract (2026-10-16): the templates were round-tripped (extracted, markers applied) into the
wpf,winforms-mvcandwinforms-mvvmrenders under$TMPDIR/iru-verify/windows/ui-contract/, which then built with 0 unsuppressed SARIF results,dotnet format --verify-no-changesclean, Core tests unchanged (9/13/9 passed);UiTestOptions+ the WinUIApp.xaml.cshook compiled in the WinUI C# probe (stopping atMakePri.exeas before). Findings baked in: the WPFThemeModeassignment needs the scoped#pragma warning disable WPF0001; an unqualifiedCaptureis ambiguous withSystem.Text.RegularExpressions.Capture(CS0104); FlaUI'sLaunch(exe, arguments)takes a non-nullablestring(CS8604 withnull); SA1515 wants a blank line before the WinForms comment. Unverified (needs Windows): the RTL/theme rendering itself, the captures and the Axe.Windows scans. - Unverified locally, never a blocker: running any of the apps, the FlaUI tests,
makeappx/signtool, MSIX install/sideload, the WinUI build and package, Store/WinGet/App Installer — all need Windows (and certificates or Partner Center for signing/submission).
Finish with a Warn explicitly block:
- Review every generated file before building, committing or distributing; replace the placeholder icon/tiles (Store certification rejects placeholder artwork) and the greeting sample with real code.
- Run
iru-setup-windows-gitignore—bin/,obj/,TestResults/,artifacts/,build/,docfx/api/,*.pfx/*.p12and MSIX outputs must never be committed. - Never commit a signing certificate or its password;
build-msix.ps1reads them from parameters or theWINDOWS_CERTIFICATE_PATH/WINDOWS_CERTIFICATE_PASSWORDenvironment variables (CI decodes them from secrets), and an unsigned MSIX cannot be installed by users (the Store re-signs, so Store submissions may be unsigned). - The MSIX
publishermust equal the signing certificate's subject (or the Partner Center publisher for the Store), andpackage-namemust match the Store reservation — fix both before the first release. - With
sonar≠none, aSONAR_TOKENsecret and the Sonar project must exist before CI analysis works.