This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
CimianStudio is a WinUI 3 / .NET 10 admin GUI for managing Cimian software-deployment repositories (the Windows analogue of MunkiAdmin). It ships as a signed MSI; CI is unsigned-only because hosted runners can't access the enterprise cert.
Project-wide rules in the parent repo's CLAUDE.md (signing, sudo over RunAs, worktree-first workflow, no-emoji commit messages, don't push without explicit instruction) apply here too.
build.ps1 is the supported entry point — Defender ASR rule 01443614-cd74-433a-b99e-2ecdc07bfc25 blocks freshly-built unsigned exes on the dev box, so signing is mandatory unless you've added a Defender exclusion.
.\build.ps1 # Debug/x64, signs by default
.\build.ps1 -Run # build + sign + launch
.\build.ps1 -Configuration Release -Architecture arm64 -Clean
.\build.ps1 -Release # full pipeline: publish + sign + cimipkg MSI + sign MSI (x64 + arm64)
.\build.ps1 -Release -Architecture x64 -Install # build signed MSI and msiexec it on this host
.\build.ps1 -Release -Upload v0.3.0 # push signed artifacts to GitHub release, clobbering unsigned CI uploads
.\build.ps1 -NoSign # only use when targeting a different host
$env:CIMIAN_CERT_SUBJECT = 'YourOrg'; .\build.ps1 # override cert subject (no default; must be set)Quick dotnet iteration without packaging:
dotnet restore CimianStudio.sln
dotnet build CimianStudio.sln -c Release
dotnet test CimianStudio.sln -c Release
dotnet test tests/CimianStudio.Core.Tests/CimianStudio.Core.Tests.csproj # single project
dotnet test --filter "FullyQualifiedName~Canary" # idempotency canary on real deployment filesTreatWarningsAsErrors=true is set in Directory.Build.props for every project — silenced rules are listed there with reasons, don't add new NoWarn entries without one.
Invoke-Release shells out to cimipkg.exe to produce the MSI. Get-CimipkgPath searches in this order: sibling ../CimianTools/release/{x64,arm64}/cimipkg.exe, then PATH, then a pinned GitHub download. It refuses to run any cimipkg.exe whose Authenticode subject doesn't contain YourOrg (override via $env:CIMIPKG_EXPECTED_SUBJECT). For the GitHub download path you must set $env:CIMIPKG_VERSION to a release tag — there's no "latest" fallback by design (supply-chain pin).
Standard clean-architecture layering with WinUI 3 on top. DI graph is wired in src/CimianStudio/App.xaml.cs using Microsoft.Extensions.Hosting — that's the canonical place to look for "where does this service come from."
src/CimianStudio/— WinUI 3 host.App.xaml.csbuilds theIHost, registers services as singletons and view-models/pages as transients.GitPageandImportPageare deliberately singletons so cross-tab handoffs (Import → Git, Packages drop → Import) operate on the live page instance.src/CimianStudio.Core/— Domain models (Models/{Packages,Manifests,Catalogs,Predicates,Repository,Git,Search}) and service interfaces (I*Service,ISessionState). No infrastructure dependencies.src/CimianStudio.Infrastructure/— Implementations:{Package,Manifest,Catalog,Repository,Git,Search}Service, the thinYaml/PackageYaml.csshim around upstreamYamlUtils(handles only the_metadataunderscore-alias workaround and script trailing-newline normalization),Import/WinUIImportPrompter.cs(GUI-sideIImportPrompteradapter),EditorSessionState, settings persistence.src/CimianStudio.Shared/— Constants and settings types shared across layers. (TheCA1716namespace-warning is intentionally silenced because of the nameShared.)tests/CimianStudio.{Core,Infrastructure}.Tests/— xUnit + FluentAssertions + Moq.
MVVM uses CommunityToolkit.Mvvm source generators ([ObservableProperty], [RelayCommand]). Models that round-trip through YAML use List<T> and public setters by design (CA1002/CA2227 silenced for that reason).
App.PendingPackageSelection / App.PendingManifestSelection are one-shot statics consumed by the next-loaded PackagesPage / ManifestsPage. Use them for cross-page navigation (e.g. catalog row → open in package editor); don't promote them to long-lived state.
CimianStudio links two upstream Cimian shared libraries via ..\..\..\..\packages\CimianTools\shared\ ProjectReferences (sibling-submodule layout under a parent Cimian/ folder):
Cimian.Core.csproj(inCimianStudio.Infrastructure.csproj) — hostsYamlUtils, the single source of truth for pkginfo/manifest/catalog YAML across every Cimian tool. CimianStudio routes through it via the thinPackageYamlshim. If you find yourself reaching for a parallel YAML serializer here, stop — fixYamlUtilsupstream instead.Cimian.Import.csproj(in bothCimianStudio.csprojandCimianStudio.Infrastructure.csproj) — hostsMetadataExtractor,IImportPrompter, andImportService.ImportAsync. The wizard (Views/Import/ImportPage.xaml.cs) drives the UX, then hands collected state toImportService.ImportAsyncviaWinUIImportPrompterfor the actual disk write (hash, copy, pkginfo serialize). Same canonical form ascimiimport.
CI mirrors this with .github/workflows/ci.yml cloning windowsadmins/cimian into the four-levels-up slot. If you rename or move that path, update both csproj files and the CI workflow.
The Views/Import/ImportPage.xaml.cs wizard is purely UI: drag-drop, queue, four-step form (review, edit metadata, scripts, location+preview). On Save it writes any user-edited script content to temp files, builds a WinUIImportPrompter holding the collected state, and calls ImportService.ImportAsync(...) — the upstream orchestrator handles template lookup (against All.yaml), file hash, installer copy, and pkginfo write. The wizard then refreshes its preview from the file ImportService just wrote (pkginfo location is derived in ComputeImportedPaths, mirroring ImportService's filename scheme — keep them in sync).
PackageService.NotifyPackagesChanged() is the manual nudge the wizard sends after ImportService returns, since ImportService writes the pkginfo directly and bypasses PackageService.CreatePackageAsync (which would have fired the event automatically).
build.ps1 mirrors CimianTools/build.ps1:
- Looks up cert by subject (default
YourOrg, override$env:CIMIAN_CERT_SUBJECT), prefersCurrentUser\My, falls back toLocalMachine\My. - Tries in-process
signtoolagainst three RFC3161 TSAs in order (DigiCert, Sectigo, Entrust). If ASR denies modification of the fresh binary, falls back tosudo signtool— one elevated invocation that signs every file in one shot, not one UAC prompt per file. Don't refactor this into per-fileInvoke-SignArtifactcalls during release builds. - The full release pipeline signs CimianStudio-shipped binaries only and leaves Microsoft.*/Windows App SDK runtime DLLs alone — re-signing them invalidates Microsoft's signatures.