This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build the whole solution:
dotnet build ExperimentFramework.slnxRun all tests (excluding e2e/screenshot tests):
dotnet test ExperimentFramework.slnx --filter "Category!=docs-screenshot"CI uses the MSBuild flag -p:ExcludeE2ETests=true for the coverage run; the filter above is the local equivalent.
Run a single test by name:
dotnet test ExperimentFramework.slnx --filter "FullyQualifiedName~SomeTestClassName"Run e2e screenshot tests (requires a running DashboardHost):
Terminal 1 — start the host with seeded demo data:
dotnet run --project samples/ExperimentFramework.DashboardHost -- \
--seed=docs --freeze-date 2026-04-01T12:00:00Z --Urls=http://localhost:5195Terminal 2 — run the screenshot suite once the host is ready:
E2E__BaseUrl=http://localhost:5195 dotnet test tests/ExperimentFramework.E2E.Tests \
--filter "Category=docs-screenshot"Install Playwright browsers (required once after building E2E tests):
pwsh tests/ExperimentFramework.E2E.Tests/bin/Debug/net10.0/playwright.ps1 install chromiumBuild the DocFX site:
dotnet tool install -g docfx # once
docfx build docs/docfx.json
# output at docs/_site/Run the sample host with demo data (interactive dev):
dotnet run --project samples/ExperimentFramework.DashboardHost -- \
--seed=docs --Urls=http://localhost:5195
# dashboard at http://localhost:5195/dashboardThe src/ folder is a modular NuGet family. Groups by purpose:
| Group | Projects |
|---|---|
| Core | ExperimentFramework (proxy/dispatch engine), ExperimentFramework.Generators (Roslyn source generator, targets netstandard2.0) |
| Dashboard | ExperimentFramework.Dashboard (middleware + AddExperimentDashboard), ExperimentFramework.Dashboard.UI (Blazor Razor components, RCL), ExperimentFramework.Dashboard.Api (REST endpoints), ExperimentFramework.Dashboard.Abstractions (interfaces: IAnalyticsProvider, IDashboardDataProvider, etc.), ExperimentFramework.Dashboard.Analytics |
| Governance | ExperimentFramework.Governance (lifecycle, approvals, policy-as-code), ExperimentFramework.Governance.Persistence (IGovernancePersistenceBackplane, in-memory impl), ExperimentFramework.Governance.Persistence.Redis, ExperimentFramework.Governance.Persistence.Sql |
| Data plane | ExperimentFramework.DataPlane.Abstractions (IDataBackplane), ExperimentFramework.DataPlane, ExperimentFramework.DataPlane.SqlServer, ExperimentFramework.DataPlane.Kafka, ExperimentFramework.DataPlane.AzureServiceBus |
| Selection modes | ExperimentFramework.FeatureManagement, ExperimentFramework.OpenFeature, ExperimentFramework.StickyRouting |
| Algorithms | ExperimentFramework.Bandit, ExperimentFramework.Rollout, ExperimentFramework.Targeting, ExperimentFramework.AutoStop |
| Science | ExperimentFramework.Science (t-test, chi-square, Mann-Whitney, ANOVA, effect size, power analysis), ExperimentFramework.Data (outcome recording) |
| Infrastructure | ExperimentFramework.Resilience (Polly circuit breaker/timeout), ExperimentFramework.Distributed + .Redis, ExperimentFramework.Diagnostics (InMemory/Logger/OTel event sinks), ExperimentFramework.Metrics.Exporters (Prometheus/OTel), ExperimentFramework.Audit, ExperimentFramework.Admin |
| Extensibility | ExperimentFramework.Plugins (dynamic assembly loading, hot reload), ExperimentFramework.Plugins.Generators, ExperimentFramework.Simulation (shadow mode), ExperimentFramework.Configuration (YAML/JSON DSL), ExperimentFramework.Testing |
The solution file is ExperimentFramework.slnx. The benchmarks/ and samples/ projects are included in the solution but are not part of the NuGet package graph.
Experiments are defined at startup, not at runtime. There is no API for creating experiments after app.Build(). The typical pattern:
var config = ExperimentFrameworkBuilder.Create()
.Define<IMyService>(e => e
.UsingFeatureFlag("my-flag")
.AddDefaultTrial<ControlImpl>("control")
.AddTrial<VariantImpl>("variant")
.OnErrorFallbackToControl())
.UseDispatchProxy(); // or no call = source-generated proxy
builder.Services.AddExperimentFramework(config);The fluent DSL has synonym methods: Trial<T> = Define<T>, AddControl = AddDefaultTrial, AddCondition = AddVariant. Use whichever fits your mental model.
IMutableExperimentRegistry (accessible at runtime) can only toggle active/rollout percentage on already-registered experiments. It cannot create new ones. This is a common foot-gun.
The dashboard queries IAnalyticsProvider (defined in Dashboard.Abstractions). Critical wiring notes:
IAnalyticsProvideris not registered by default. Pass it viaDashboardOptions.AnalyticsProviderinAddExperimentDashboard(options => ...).IAnalyticsProvideris not automatically fed byIDataBackplane. The two interfaces are independent; callers must bridge them if needed.- In
--seed=docsmode,DemoAnalyticsProvider(samples/ExperimentFramework.DashboardHost/Demo/DemoAnalyticsProvider.cs) is wired in directly viaDashboardOptions.
IGovernancePersistenceBackplane (in ExperimentFramework.Governance.Persistence) exposes:
SaveExperimentStateAsync, AppendStateTransitionAsync, AppendApprovalRecordAsync, AppendConfigurationVersionAsync, AppendPolicyEvaluationAsync.
Enable with services.AddInMemoryGovernancePersistence() (or Redis/SQL variants). Not enabled by default — the dashboard governance UI is non-functional without it.
| Sample | Purpose |
|---|---|
samples/ExperimentFramework.DashboardHost/ |
Canonical standalone dashboard host. Supports --seed=docs (5 experiments across lifecycle states, real analytics, governance records, 3 policies) and --freeze-date <ISO> for deterministic screenshots. |
samples/ExperimentFramework.SampleWebApp/ |
Minimal ASP.NET web app integration |
samples/ExperimentFramework.ComprehensiveSample/ |
Showcase of most features |
samples/ExperimentFramework.SimulationSample/ |
Shadow mode / simulation |
samples/ExperimentFramework.AspireDemo/ |
.NET Aspire multi-project demo |
| Other samples | BanditOptimizer, ResilienceDemo, FeatureFlagDemo, ScientificDemo, ScientificSample, PluginHostSample |
These are production foot-guns in the DashboardHost setup:
DashboardHost.csprojsets<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>— without this,/_framework/blazor.web.jsreturns 404 (the host has no.razorfiles of its own; they live in theDashboard.UIRCL).- Use
MapRazorComponents<App>().AddInteractiveServerRenderMode(). Do not callMapBlazorHub()— that is legacy Blazor Server and will conflict. - SSR prerender requires data loading in
OnInitializedAsync, notOnAfterRenderAsync. - Dashboard REST API is mounted at
/dashboard/api/...;ExperimentApiClientbase address must include the/dashboard/prefix or relative paths will 404.
The DocFX site lives under docs/; output is docs/_site/.
Docs structure:
docs/user-guide/— operator-guide (tutorials + reference) and developer-setup walkthroughsdocs/reference/— conceptual reference docs (bandit, circuit-breaker, data-backplane, etc.)docs/api/— DocFX-generated API referencedocs/superpowers/specs/— design documents for major features (not published)
Screenshot pipeline:
- PNGs live in
docs/images/screenshots/<area>/and are committed to the repo - Reqnroll step
When I capture screenshot "<name>"(inDocsScreenshotStepDefinitions.cs) writes todocs/images/screenshots/{area}/{name}.png [BeforeScenario("docs-screenshot", Order = 5)]hook (inDocsScreenshotHooks.cs) sets 1280×800 viewport and injects determinism CSS (no animations, no transitions)- Feature files tag scenarios
@docs-screenshot @screenshot-area:<area>; the area tag value becomes the subdirectory - Design rationale:
docs/superpowers/specs/2026-04-22-user-guide-and-screenshots-design.md
Workflows live in .github/workflows/:
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
push/PR to main |
Build + test (no E2E) with coverage; on PR adds Codecov report and sticky coverage comment. On push to main: full release job — GitVersion, pack all NuGet packages, push to NuGet.org and GitHub Packages, create GitHub Release with generated release notes. |
docs.yml |
push to main |
Builds DocFX site and deploys to GitHub Pages. |
docs-screenshots.yml |
weekly Monday 03:00 UTC + workflow_dispatch |
Starts DashboardHost with --seed=docs, runs Category=docs-screenshot suite, optionally compresses PNGs with oxipng, opens a drift PR if any screenshot changed. |
codeql-analysis.yml |
push/PR to main + schedule |
CodeQL security scanning. |
dependency-review.yml |
PR | Dependency review. |
update-packages.yml |
schedule | Automated dependency updates. |
stale.yml |
schedule | Marks stale issues and PRs. |
labeler.yml |
PR | Auto-labels PRs. |
- Multi-targeting: Most
src/projects targetnet8.0;net9.0;net10.0.ExperimentFramework.Generatorstargetsnetstandard2.0(Roslyn analyzers must). - Solution format:
ExperimentFramework.slnxis the new XML-format solution file, not.sln. Pass it explicitly todotnet build/test/pack. - E2E test framework: Playwright + Reqnroll (Gherkin BDD) with xUnit runner. A Gherkin tag
@docs-screenshotmaps to xUnit TraitCategory=docs-screenshot(lowercase, hyphenated). The filter string isCategory=docs-screenshot, notDocsScreenshotorCategory=DocsScreenshot. - Versioning: GitVersion drives package versions from git history. The
releaseCI job calculates and injects version numbers; local builds use the placeholder1.0.0in the csproj. - Commit style: Conventional commits (
feat(scope): ...,fix:,docs:,chore(deps):) with aCo-Authored-By:footer. Seegit logfor examples. ExcludeE2ETestsMSBuild property: The E2E project references Playwright and requires a live host. CI sets-p:ExcludeE2ETests=truein the main coverage run to skip it; thee2e-testsjob inci.ymlhandles it separately (markedcontinue-on-error).