maui-labs
Copilot instructions228 starsChanged 54 days agoMITView on GitHub
These instructions guide GitHub Copilot and other AI code generation tools when working with the maui-labs repository. DevFlow targets multiple platforms via multi-targeting. The pattern: Use #if directives for platform code in multi-targeting projects: Important: Both .ios.cs and .maccatalyst.cs files compile for Mac Catalyst. Use #if IOS || MACCATALYST when code applies to both. When adding new features to the DevFlow agent: MCP tools live in src/Cli/Microsoft.Maui.Cli/DevFlow/Mcp/Tools/. When creating a new tool: Agent HTTP endpoints are defined in DevFlowAgentService.cs (in…
- Installs packages
We split this copilot-instructions.md into the rules it gives an agent
17 rules. About 0 are common, 6 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
Use #if directives for platform code in multi-targeting projects:
- Distinctive: line 33
When adding new features to the DevFlow agent:
- Very specific: line 58
Create a new file in the Tools/ directory
- Very specific: line 59
Use [McpServerToolType] on the class, [McpServerTool] on the method
- Very specific: line 63
Use session.GetAgentClientAsync(agentPort) for agent resolution
- Very specific: line 106
Use Option<T> for named parameters, Argument<T> for positional
- Very specific: line 108
Use SetHandler with InvocationContext for commands with many options
- Distinctive: line 135
Document breaking changes in the PR description
- Very specific: line 136
Add the breaking-change label to the PR
- Distinctive: line 137
Update parameter defaults so existing callers keep working where possible (add new params with defaults at the end)
- Very specific: line 151
IsShippingandIsPackable: default false in Directory.Build.props. - Distinctive: line 210
If the upstream fix removes the need for a workaround in our code, simplify the CLI code accordingly in the same PR.
- Very specific: line 214
After updating Xamarin.Apple.Tools.MaciOS or making changes to src/Cli/.../Providers/Apple/ or src/Cli/.../Commands/AppleCommands.cs, run …
- Distinctive: line 235
When adding a new product to this repo you must set up two CI surfaces: a GitHub Actions workflow for PR validation and a build job + …
- Very specific: line 244
Create .github/workflows/ci-{product}.yml.
- Distinctive: line 421
Signing: All shipped NuGet packages must build on Windows so MicroBuild/ESRP can sign the DLLs.
- Very specific: line 422
publishFeedCredentials: Always use 'nuget.org (dotnetframework)' — this is the service connection configured in the Azure DevOps project.
≈ “Common” is an estimate: at least 1 in 5 of the 3,669 Copilot instructions files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Copilot Instructions for maui-labs
These instructions guide GitHub Copilot and other AI code generation tools when working with the maui-labs repository.
Platform-Specific Code
DevFlow targets multiple platforms via multi-targeting. The pattern:
Microsoft.Maui.DevFlow.Agent.Abstractionstargetsnet10.0— all platform-agnostic code lives here (HTTP server, routing,DevFlowAgentServicebase class). No MAUI dependency.Microsoft.Maui.DevFlow.Agent.Coretargetsnet10.0— MAUI UI backend (MauiDevFlowAgentService,VisualTreeWalker). Depends onAgent.Abstractions.Microsoft.Maui.DevFlow.Agenttargetsnet10.0-android,net10.0-ios,net10.0-maccatalyst,net10.0-macos,net10.0-windows10.0.19041.0— platform-specific overrides.Microsoft.Maui.DevFlow.Agent.Gtktargetsnet10.0— Linux/GTK-specific code.
Use #if directives for platform code in multi-targeting projects:
#if IOS || MACCATALYST
// iOS and Mac Catalyst share UIKit
return uiWindow.Screen.Scale;
#elif ANDROID
return activity.Resources?.DisplayMetrics?.Density ?? 1.0;
#elif WINDOWS
return xamlRoot.RasterizationScale;
#elif MACOS
return nsWindow.BackingScaleFactor;
#endifImportant: Both .ios.cs and .maccatalyst.cs files compile for Mac Catalyst. Use #if IOS || MACCATALYST when code applies to both.
Agent Architecture (Abstractions/Core/Platform Pattern)
When adding new features to the DevFlow agent:
- Add the virtual method in
Agent.Abstractions/DevFlowAgentService.cs— this is the platform-agnostic base - Override in
Agent.Core/MauiDevFlowAgentService.csfor MAUI-specific behavior - Override in
Agent/DevFlowAgentService.cswith#ifdirectives for platform-specific behavior - Override in
Agent.Gtk/GtkAgentService.csfor Linux/GTK if needed
Example:
// In Agent.Abstractions/DevFlowAgentService.cs
protected virtual Task<byte[]?> CaptureFullScreenAsync() => Task.FromResult<byte[]?>(null);
// In Agent/DevFlowAgentService.cs
#if IOS || MACCATALYST
protected override Task<byte[]?> CaptureFullScreenAsync()
=> DispatchAsync(() => CaptureAllWindowsComposited());
#elif MACOS
protected override async Task<byte[]?> CaptureFullScreenAsync() { /* AppKit capture */ }
#endifMCP Tool Conventions
MCP tools live in src/Cli/Microsoft.Maui.Cli/DevFlow/Mcp/Tools/. When creating a new tool:
- Create a new file in the
Tools/directory - Use
[McpServerToolType]on the class,[McpServerTool]on the method - Every parameter must have a
[Description]— this is what AI agents see - Tool names use the
maui_prefix and snake_case:maui_screenshot,maui_tap - First parameter is always
McpAgentSession session - Use
session.GetAgentClientAsync(agentPort)for agent resolution - Register the tool in
Mcp/McpServerHost.cs:.WithTools<YourTool>()
[McpServerToolType]
public sealed class MyNewTool
{
[McpServerTool(Name = "maui_my_action"),
Description("Clear description of what this tool does and when to use it.")]
public static async Task<string> MyAction(
McpAgentSession session,
[Description("Agent HTTP port (optional if only one agent connected)")] int? agentPort = null,
[Description("Describe this parameter clearly")] string requiredParam,
[Description("Describe this optional parameter")] string? optionalParam = null)
{
var agent = await session.GetAgentClientAsync(agentPort);
// Use agent.* methods
return "Result";
}
}HTTP Endpoint Conventions
Agent HTTP endpoints are defined in DevFlowAgentService.cs (in Agent.Abstractions). When adding new endpoints:
- Register the route in the
RegisterRoutes()method:_server.MapGet("/api/myendpoint", HandleMyEndpoint); - Implement the handler as
protected virtual async Task<HttpResponse> HandleMyEndpoint(HttpRequest request) - For POST endpoints that accept JSON bodies, define a DTO class at the bottom of the file
- Add a corresponding method in
AgentClient(inMicrosoft.Maui.DevFlow.Client) — this is the public API
// In DevFlowAgentService.cs — handler
protected virtual async Task<HttpResponse> HandleMyEndpoint(HttpRequest request) { ... }
// In AgentClient.cs — public API (this is what NuGet consumers use)
public async Task<MyResult?> MyEndpointAsync(string param) { ... }CLI Command Conventions
CLI commands use System.CommandLine in Program.cs:
- Use
Option<T>for named parameters,Argument<T>for positional - Support
--json/--no-jsonoutput modes viaOutputWriter - Use
SetHandlerwithInvocationContextfor commands with many options - Post-action flags:
--and-screenshot,--and-tree,--and-tree-depthfor verification after mutations
Client and Driver Libraries (AgentClient)
Microsoft.Maui.DevFlow.Client/AgentClient.cs is the public API for NuGet consumers. It lives
in the portable Microsoft.Maui.DevFlow.Client package (netstandard2.0 + modern .NET) so .NET
Framework harnesses share the exact protocol behavior; Microsoft.Maui.DevFlow.Driver references it
and keeps the platform/native concerns (app process management, UI Automation, Skia, recording).
Changes to method signatures are:
- Binary breaking — existing compiled code stops working
- Source breaking — existing source code fails to compile
Rules when working in this area:
- Protocol DTOs are defined once, in Client. Never re-declare one in Driver or a test harness.
- Everything in Client must compile for
netstandard2.0. Where a modern API has no portable equivalent (e.g.SocketsHttpHandler.ConnectCallback), guard it with#if DEVFLOW_NETSTANDARDand supply a portable path — do not fork the DTOs or the client surface. - Missing BCL overloads belong in
Client/Compatibility/NetStandardCompat.csas extension methods with the exact framework signature, so shared sources stay free of#if. - Types keep the
Microsoft.Maui.DevFlow.Drivernamespace and are re-exported from the Driver assembly viaDriver/TypeForwards.cs. Adding a new public type to Client means adding a forward. - Anything platform-specific or native stays in Driver. Do not grow the portable surface with it.
The repo is at 0.1.0-preview so breaking changes are acceptable, but:
- Document breaking changes in the PR description
- Add the
breaking-changelabel to the PR - Update parameter defaults so existing callers keep working where possible (add new params with defaults at the end)
Test Patterns
- Framework: xUnit v2.9.3
- Naming:
MethodName_Condition_ExpectedResultor descriptive[Fact]names - Location:
src/DevFlow/Microsoft.Maui.DevFlow.Tests/ - Coverage: coverlet.collector
- Approach: DevFlow tests use real Agent.Core code — they instantiate actual services and test behavior
Arcade SDK Gotchas
- Never modify
eng/common/— auto-generated by Arcade SDK, overwritten by Dependency Flow - Central Package Management: specify package versions only in
Directory.Packages.props, not in.csprojfiles IsShippingandIsPackable: defaultfalseinDirectory.Build.props. Shipped projects explicitly settrue.- Signing: configured in
eng/Signing.props. New third-party DLLs need a3PartySHA2entry. - Version: defined in
eng/Versions.props(VersionPrefix+VersionSuffix). Per-product overrides insrc/{Product}/Version.props.
npm Dependencies and Supply Chain
The JavaScript in this repo ships (the DevFlow Inspector client and VS Code host are packaged and distributed), so vulnerable transitive dependencies reach users. Catch them locally, before opening the PR — not weeks later via a Dependabot advisory PR.
Whenever you add, update, or remove an npm dependency, or regenerate a lockfile:
node eng/scripts/audit-npm.mjsThis audits every npm project in the repo and verifies each is registered in
.github/dependabot.yml. Exit 0 = clean. Exit 1 = high/critical advisories, an
unregistered project, or a missing lockfile. Exit 2 = a project could not be checked against
the advisory database. Resolve all three before opening the PR.
- Exit
2is not a pass.npm auditalso exits non-zero when it cannot reach the registry, so that result means no signal.--allow-unverifieddowngrades it for offline work; CI never passes that flag and so fails closed. - Register every new
package.jsonin.github/dependabot.ymlin the same PR. Dependabot security alerts fire repo-wide, but routine version updates only run for listed directories. An unregistered project drifts silently until advisories pile up. - Commit a
package-lock.json. Without one there is no resolved graph to audit. devDependenciescount too — they run on CI runners with repo credentials in scope.- Fix with
npm audit fixin the reported directory, re-runnpm ci && npm test, and commit the updatedpackage-lock.json. Remediation is required; a known advisory left in place keeps the check red and blocks later dependency PRs.
Full guidance: .github/instructions/dependencies.instructions.md.
Dependency Updates (darc)
Upstream dependencies are managed via darc/Maestro. When updating a dependency:
Updating Xamarin.Apple.Tools.MaciOS
darc update-dependencies --channel ".NET 10.0.1xx SDK" --name Xamarin.Apple.Tools.MaciOSThis updates both eng/Version.Details.xml (SHA + version) and eng/Versions.props (XamarinAppleToolsMaciOSVersion).
After every update, check the commits between the old and new SHA for changes that affect CLI behavior:
# Compare old..new SHA from Version.Details.xml
gh api repos/dotnet/macios-devtools/compare/{oldSha}...{newSha} --jq '.commits[] | .commit.message | split("\n")[0]'Look for:
- New public APIs on
EnvironmentChecker,AppleInstaller,SimulatorService,RuntimeServicethat the CLI could leverage - Bug fixes to simctl JSON parsing, stdout pollution, or ILMerge compatibility that previously required workarounds in our code
- Breaking changes to method signatures the CLI calls (would require code updates)
If the upstream fix removes the need for a workaround in our code, simplify the CLI code accordingly in the same PR.
Post-Update Smoke Tests (macOS only)
After updating Xamarin.Apple.Tools.MaciOS or making changes to src/Cli/.../Providers/Apple/ or src/Cli/.../Commands/AppleCommands.cs, run the Apple CLI smoke tests on macOS to verify nothing regressed:
./eng/smoke-tests/apple-cli-smoke-test.shThe script builds the CLI and runs these checks:
maui apple xcode list --json— detects installed Xcodemaui apple runtime list --json— lists simulator runtimesmaui apple simulator list --json— lists available simulatorsmaui apple simulator start <name> --json— boots a simulatormaui apple simulator stop <name> --json— shuts it downmaui --dry-run apple install --json— validates install flow (iOS default)maui --dry-run apple install --platform all --json— validates all-platform install
You can also pass a pre-built binary: ./eng/smoke-tests/apple-cli-smoke-test.sh path/to/maui
Note: These tests require macOS with Xcode installed. They are skipped automatically on other platforms. If you are not on macOS, skip this step — CI on macOS runners will catch regressions.
CI/CD — New Product Checklist
When adding a new product to this repo you must set up two CI surfaces: a GitHub Actions workflow for PR validation and a build job + publish stage in the Azure DevOps official pipeline for signing and NuGet.org publishing.
You must also provide documentation:
- Product README — create two READMEs: (1) a contributor README at the product root (
src/{Product}/README.md) for GitHub browsing with features, build instructions, and architecture; (2) a NuGet README next to the shipping csproj (src/{Product}/Microsoft.Maui.{Product}/README.md) with install, quick start, and usage examples. Pack the NuGet README via<None Include="README.md" Pack="true" PackagePath="/" />and set<PackRepoRootReadme>false</PackRepoRootReadme>. Both should include: product name, features, platform support matrix, quick start, packages, requirements, and experimental status warning. Keep descriptions aligned to avoid drift. Images in NuGet READMEs must use absolute URLs (e.g.,https://raw.githubusercontent.com/dotnet/maui-labs/main/...) — relative paths break on NuGet.org since images aren't inside the.nupkg. - Root README entry — add a section under
## Productsin the repo-rootREADME.mdwith a brief description, feature highlights, and package table.
Step 1: GitHub Actions PR / Push Workflow
Create .github/workflows/ci-{product}.yml. Use the template below — replace every {Product} (PascalCase) and {product} (lowercase) placeholder.
name: CI - {Product}
on:
push:
branches: [main]
paths:
- 'src/{Product}/**'
# CUSTOMIZE: add paths for cross-product ProjectReference dependencies, e.g.:
# - 'src/OtherProduct/SharedLib/**'
- 'eng/**'
- 'Directory.Build.props'
- 'Directory.Build.targets'
- 'Directory.Packages.props'
- 'global.json'
- 'NuGet.config'
pull_request:
# IMPORTANT: 'edited' is required — without it CI does not run when
# GitHub auto-retargets a PR after a stacked branch merges.
types: [opened, synchronize, reopened, edited]
branches: [main]
paths:
- 'src/{Product}/**'
# CUSTOMIZE: add paths for cross-product ProjectReference dependencies, e.g.:
# - 'src/OtherProduct/SharedLib/**'
- 'eng/**'
- 'Directory.Build.props'
- 'Directory.Build.targets'
- 'Directory.Packages.props'
- 'global.json'
- 'NuGet.config'
jobs:
build:
uses: ./.github/workflows/_build.yml
with:
project-path: src/{Product}/{Product}.slnf # CUSTOMIZE: path to solution filter (.slnf or .slnx)
project-name: {product} # CUSTOMIZE: lowercase, used in artifact names
run-tests: true
pack: true
install-workloads: true # CUSTOMIZE: set false for net10.0-only products (no MAUI TFMs)
# os: '["macos-latest", "windows-latest"]' # CUSTOMIZE: default is macOS + Windows
# native-deps: 'sudo apt-get install ...' # CUSTOMIZE: if Linux-only with native libs_build.yml inputs reference
| Input | Default | When to change |
install-workloads | true | Set false if the product targets only net10.0 (no MAUI TFMs) |
os | ["macos-latest", "windows-latest"] | Override to ["ubuntu-24.04"] for Linux-only products |
native-deps | (empty) | Provide an apt-get install command if the product needs native libraries (e.g., GTK4) |
pack | false | Set true if the product produces NuGet packages |
run-tests | true | Set false only if there are no tests yet |
Step 2: Azure DevOps Official Pipeline
The official pipeline is eng/pipelines/devflow-official.yml. It handles Arcade-based builds with MicroBuild/ESRP signing and NuGet.org publishing. For each new product, add three blocks:
a) Publish parameter (at the top, in the parameters: section)
- name: publish{Product}Nuget
displayName: 'Publish {Product} packages to NuGet.org'
type: boolean
default: falseb) Build job (in the build stage, under jobs:, parallel with existing jobs)
- job: {Product}
displayName: {Product} - Windows
pool:
name: NetCore1ESPool-Internal
demands: ImageOverride -equals windows.vs2026.amd64
strategy:
matrix:
Release:
_BuildConfig: Release
_OfficialBuildArgs: /p:DotNetSignType=$(_SignType)
/p:TeamName=$(_TeamName)
/p:OfficialBuildId=$(BUILD.BUILDNUMBER)
steps:
- task: UseDotNet@2
displayName: Install .NET SDK
inputs:
useGlobalJson: true
# CUSTOMIZE: If the product needs MAUI workloads, add these steps
# before the build (copy from the DevFlow job):
# - Install MAUI workloads through Arcade's wrapper
# (eng\common\dotnet.cmd workload install maui ...)
# - Install Android SDK dependencies through eng/common/dotnet.ps1
# in a pwsh step so quoted paths remain a single argument
- script: eng\common\cibuild.cmd
-configuration $(_BuildConfig)
-prepareMachine
-projects $(Build.SourcesDirectory)\src\{Product}\{Product}.slnf
$(_OfficialBuildArgs)
displayName: Build and Test {Product}SDK versioning: Use
UseDotNet@2with an explicitversion:matching the repo-rootglobal.jsonSDK version, notuseGlobalJson: true— the latter scans the entire checkout and can find nestedglobal.jsonfiles (e.g. Comet's .NET 11 preview) that break non-Comet jobs.Workload versioning: Always pin workload installs with
--version <pinned>. Check_build.ymlfor the current pinned version. Unpinned installs cause version drift between CI and official builds.macOS/Apple builds: Products targeting Apple TFMs (
net10.0-macos,-ios,-maccatalyst) that are pure managed C# can build on Windows — the macOS workload provides Apple reference assemblies for cross-compilation, and MicroBuild signs on Windows automatically. Products with native code (e.g. Swift bindings) require a two-stage build: a macOS job that compiles native artifacts, then a Windows job that downloads them and packs/signs. Usepool: { name: Azure Pipelines, vmImage: macos-latest-internal, os: macOS }withtemplateContext: outputs:for the macOS stage. Addsudo xcode-selectbefore workload install — check https://aka.ms/xcode-requirement for the required Xcode version. See theEssentialsAI_macOS/EssentialsAIjob pair for the native two-stage pattern, and theMacOSjob for the pure managed single-stage pattern.
c) Conditional publish stage (at the bottom, after the other publish_*_nuget stages)
This stage filters the product's .nupkg files from the shared PackageArtifacts artifact, then pushes them to NuGet.org via the 1ES.PublishNuget task.
# Publish {Product} packages to NuGet.org
- ${{ if eq(parameters.publish{Product}Nuget, true) }}:
- stage: publish_{product}_nuget
displayName: 'Publish {Product} to NuGet.org'
dependsOn:
- Validate
- publish_using_darc
jobs:
- job: PrepareArtifacts
displayName: 'Prepare {Product} Artifacts'
timeoutInMinutes: 15
pool:
name: NetCore1ESPool-Internal
image: windows.vs2026.amd64
os: windows
templateContext:
outputs:
- output: pipelineArtifact
displayName: Publish {Product} Packages
targetPath: '$(Pipeline.Workspace)/{Product}Packages'
artifactName: {Product}PackagesForNuGet
steps:
- download: current
artifact: PackageArtifacts
displayName: Download PackageArtifacts
- powershell: |
New-Item -ItemType Directory -Force -Path '$(Pipeline.Workspace)/{Product}Packages'
# CUSTOMIZE: glob must match the package ID prefix for this product
Copy-Item '$(Pipeline.Workspace)/PackageArtifacts/Microsoft.Maui.{Product}.*.nupkg' '$(Pipeline.Workspace)/{Product}Packages/' -Verbose
displayName: Filter {Product} packages
- job: PublishNuGet
displayName: 'Push {Product} to NuGet.org'
dependsOn: PrepareArtifacts
timeoutInMinutes: 30
pool:
name: NetCore1ESPool-Internal
image: windows.vs2026.amd64
os: windows
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
artifactName: {Product}PackagesForNuGet
targetPath: '$(Pipeline.Workspace)/{Product}Packages'
steps:
- task: 1ES.PublishNuget@1
displayName: 'Push {Product} to NuGet.org'
inputs:
useDotNetTask: false
packagesToPush: '$(Pipeline.Workspace)/{Product}Packages/*.nupkg'
packageParentPath: '$(Pipeline.Workspace)/{Product}Packages'
nuGetFeedType: external
publishFeedCredentials: 'nuget.org (dotnetframework)'Key conventions
- Adding a package to an existing product: update the exact
.slnfor.slnxused by that product's build job in this pipeline, not only the product's main solution. Then verify the publish stage'sCopy-Itemglob matches the new<PackageId>. A shipping project omitted from the official build input does not produce a package for release. - Package glob pattern: example:
Microsoft.Maui.{Product}.*.nupkg— use the actual<PackageId>prefix from your.csprojfiles (e.g., Linux GTK4 usesMicrosoft.Maui.Platforms.Linux.Gtk4.*.nupkg). dependsOn: [Validate, publish_using_darc]: these stages come from the Arcade post-build template (eng/common/templates-official/post-build/post-build.yml) and must always be listed.- Signing: All shipped NuGet packages must build on Windows so MicroBuild/ESRP can sign the DLLs. If the product is Linux-only, build and pack on Windows (signing), then optionally add a separate Linux verification job (see the
LinuxGtk4_LinuxVerifyjob for the pattern). publishFeedCredentials: Always use'nuget.org (dotnetframework)'— this is the service connection configured in the Azure DevOps project.
Maui.Client Conventions (Future — Not Yet Present)
A Client product (src/Client/) is planned but not yet present in this repository. When added, it will use a DI-based architecture with provider interfaces:
IAndroidProvider— discovers and installs Android SDK componentsIJdkManager— manages JDK installationsIDeviceManager— lists and manages Android emulatorsIDoctorService— runs environment health checks
Define an interface first, implement it, register in Program.Services.
Where files are headingNew Copilot instructions files have more rules about branches and pull requests, and fewer about code style.
Of the Copilot instructions files we read, the 2,297 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- comet-goSkill
- maui-devflow-debugSkill
- maui-devflow-onboardSkill
- maui-platform-backendSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

