diff --git a/.github/workflows/ci-cli.yml b/.github/workflows/ci-cli.yml
index 3fd2cd9ed..ddffd5aaf 100644
--- a/.github/workflows/ci-cli.yml
+++ b/.github/workflows/ci-cli.yml
@@ -4,7 +4,8 @@ on:
push:
branches: [main]
paths:
- - '.claude/skills/**'
+ - 'plugins/dotnet-maui/skills/maui-devflow-onboard/**'
+ - 'plugins/dotnet-maui/skills/maui-devflow-debug/**'
- 'src/Cli/**'
- 'src/DevFlow/Microsoft.Maui.DevFlow.Driver/**'
- 'eng/**'
@@ -17,7 +18,8 @@ on:
types: [opened, synchronize, reopened, edited]
branches: [main]
paths:
- - '.claude/skills/**'
+ - 'plugins/dotnet-maui/skills/maui-devflow-onboard/**'
+ - 'plugins/dotnet-maui/skills/maui-devflow-debug/**'
- 'src/Cli/**'
- 'src/DevFlow/Microsoft.Maui.DevFlow.Driver/**'
- 'eng/**'
diff --git a/MauiLabs.slnx b/MauiLabs.slnx
index d5bee3e97..bd53bb46c 100644
--- a/MauiLabs.slnx
+++ b/MauiLabs.slnx
@@ -16,6 +16,7 @@
+
diff --git a/plugins/README.md b/plugins/README.md
index c3a7c7b6e..85e6e86d9 100644
--- a/plugins/README.md
+++ b/plugins/README.md
@@ -2,7 +2,7 @@
Distributable agent skills for .NET MAUI development. Installable via the Copilot CLI, Claude Code, or VS Code plugin system.
-DevFlow runtime skills (`maui-devflow-onboard`, `maui-devflow-debug`) are bundled with the `maui` CLI from `plugins/dotnet-maui/skills/`, installed with `maui devflow init`, and exposed through the plugin manifest.
+DevFlow runtime skills (`maui-devflow-onboard`, `maui-devflow-debug`) are sourced from `plugins/dotnet-maui/skills/`, bundled into the `maui` CLI through the `Microsoft.Maui.Cli.Skills` project, installed with `maui devflow init`, and exposed through the plugin manifest.
## Plugin
diff --git a/src/Cli/Cli.slnf b/src/Cli/Cli.slnf
index 522fd1945..d19329f8a 100644
--- a/src/Cli/Cli.slnf
+++ b/src/Cli/Cli.slnf
@@ -3,6 +3,7 @@
"path": "../../MauiLabs.slnx",
"projects": [
"src\\Cli\\Microsoft.Maui.Cli\\Microsoft.Maui.Cli.csproj",
+ "src\\Cli\\Microsoft.Maui.Cli.Skills\\Microsoft.Maui.Cli.Skills.csproj",
"src\\Cli\\Microsoft.Maui.Cli.UnitTests\\Microsoft.Maui.Cli.UnitTests.csproj",
"src\\Cli\\Microsoft.Maui.StartupProfiling\\Microsoft.Maui.StartupProfiling.csproj",
"src\\DevFlow\\Microsoft.Maui.DevFlow.Driver\\Microsoft.Maui.DevFlow.Driver.csproj"
diff --git a/src/Cli/Microsoft.Maui.Cli.Skills/MauiCliSkillResources.cs b/src/Cli/Microsoft.Maui.Cli.Skills/MauiCliSkillResources.cs
new file mode 100644
index 000000000..2bb3029fb
--- /dev/null
+++ b/src/Cli/Microsoft.Maui.Cli.Skills/MauiCliSkillResources.cs
@@ -0,0 +1,18 @@
+using System.Reflection;
+
+namespace Microsoft.Maui.Cli.Skills;
+
+public static class MauiCliSkillResources
+{
+ public const string ResourceRoot = "devflow.skills";
+
+ public static Assembly Assembly => typeof(MauiCliSkillResources).Assembly;
+
+ public static IReadOnlyList BundledSkills { get; } =
+ [
+ new("maui-devflow-onboard", "MAUI DevFlow Onboard", "Guides first-time MAUI DevFlow project integration.", Recommended: true),
+ new("maui-devflow-debug", "MAUI DevFlow Debug", "Guides build, deploy, connection recovery, inspect, and debug loops with MAUI DevFlow.", Recommended: true)
+ ];
+}
+
+public sealed record MauiCliSkillDefinition(string Id, string DisplayName, string Description, bool Recommended);
diff --git a/src/Cli/Microsoft.Maui.Cli.Skills/Microsoft.Maui.Cli.Skills.csproj b/src/Cli/Microsoft.Maui.Cli.Skills/Microsoft.Maui.Cli.Skills.csproj
new file mode 100644
index 000000000..1c86840a8
--- /dev/null
+++ b/src/Cli/Microsoft.Maui.Cli.Skills/Microsoft.Maui.Cli.Skills.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net9.0;net10.0
+ Microsoft.Maui.Cli.Skills
+ Microsoft.Maui.Cli.Skills
+ Bundled agent skills for the .NET MAUI CLI
+ false
+ true
+
+
+
+
+ false
+
+
+ false
+
+
+
+
diff --git a/src/Cli/Microsoft.Maui.Cli.UnitTests/DevFlowSkillManagerTests.cs b/src/Cli/Microsoft.Maui.Cli.UnitTests/DevFlowSkillManagerTests.cs
index 9f9e7359d..a83df6ce2 100644
--- a/src/Cli/Microsoft.Maui.Cli.UnitTests/DevFlowSkillManagerTests.cs
+++ b/src/Cli/Microsoft.Maui.Cli.UnitTests/DevFlowSkillManagerTests.cs
@@ -4,6 +4,7 @@
using System.Text.Json.Nodes;
using Microsoft.Maui.Cli.DevFlow;
using Microsoft.Maui.Cli.DevFlow.Skills;
+using Microsoft.Maui.Cli.Skills;
using Xunit;
namespace Microsoft.Maui.Cli.UnitTests;
@@ -11,6 +12,20 @@ namespace Microsoft.Maui.Cli.UnitTests;
[Collection("CLI")]
public sealed class DevFlowSkillManagerTests
{
+ [Fact]
+ public void BundledSkillResources_AreLoadedFromSkillsAssembly()
+ {
+ var skillsAssembly = MauiCliSkillResources.Assembly;
+ var cliAssembly = typeof(DevFlowSkillManager).Assembly;
+ var skillsResources = skillsAssembly.GetManifestResourceNames();
+ var cliResources = cliAssembly.GetManifestResourceNames();
+
+ Assert.NotSame(cliAssembly, skillsAssembly);
+ Assert.Contains($"{MauiCliSkillResources.ResourceRoot}/maui-devflow-onboard/SKILL.md", skillsResources);
+ Assert.Contains($"{MauiCliSkillResources.ResourceRoot}/maui-devflow-debug/SKILL.md", skillsResources);
+ Assert.DoesNotContain(cliResources, resource => resource.StartsWith($"{MauiCliSkillResources.ResourceRoot}/maui-devflow-", StringComparison.Ordinal));
+ }
+
[Fact]
public async Task InstallRecommended_ProjectScope_WritesBundledSkillsAndUserLevelState()
{
diff --git a/src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj b/src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj
index f9f824fa1..ca4bfe4a1 100644
--- a/src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj
+++ b/src/Cli/Microsoft.Maui.Cli.UnitTests/Microsoft.Maui.Cli.UnitTests.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/Cli/Microsoft.Maui.Cli/DevFlow/Skills/DevFlowSkillManager.cs b/src/Cli/Microsoft.Maui.Cli/DevFlow/Skills/DevFlowSkillManager.cs
index 30cb5a864..1d97ca1eb 100644
--- a/src/Cli/Microsoft.Maui.Cli/DevFlow/Skills/DevFlowSkillManager.cs
+++ b/src/Cli/Microsoft.Maui.Cli/DevFlow/Skills/DevFlowSkillManager.cs
@@ -3,12 +3,13 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
+using Microsoft.Maui.Cli.Skills;
namespace Microsoft.Maui.Cli.DevFlow.Skills;
internal static class DevFlowSkillManager
{
- const string ResourceRoot = "devflow.skills";
+ const string ResourceRoot = MauiCliSkillResources.ResourceRoot;
const string PackageId = "Microsoft.Maui.Cli";
const string StateRootEnvironmentVariable = "MAUIDEVFLOW_STATE_ROOT";
const string AutoTarget = "auto";
@@ -19,11 +20,9 @@ internal static class DevFlowSkillManager
static readonly TimeSpan FreshnessCheckInterval = TimeSpan.FromDays(7);
static readonly TimeSpan FreshnessPromptInterval = TimeSpan.FromDays(1);
- static readonly DevFlowSkillDefinition[] s_skills =
- [
- new("maui-devflow-onboard", "MAUI DevFlow Onboard", "Guides first-time MAUI DevFlow project integration.", Recommended: true),
- new("maui-devflow-debug", "MAUI DevFlow Debug", "Guides build, deploy, connection recovery, inspect, and debug loops with MAUI DevFlow.", Recommended: true)
- ];
+ static readonly DevFlowSkillDefinition[] s_skills = MauiCliSkillResources.BundledSkills
+ .Select(skill => new DevFlowSkillDefinition(skill.Id, skill.DisplayName, skill.Description, skill.Recommended))
+ .ToArray();
static readonly string[] s_legacySkillIds =
[
@@ -463,7 +462,7 @@ static bool MigrateLegacySkills(InstallTarget installTarget, JsonObject skillSta
static async Task LoadSkillBundleAsync(DevFlowSkillDefinition skill, CancellationToken cancellationToken)
{
- var assembly = typeof(DevFlowSkillManager).Assembly;
+ var assembly = MauiCliSkillResources.Assembly;
var prefix = $"{ResourceRoot}/{skill.Id}/";
var resources = assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(prefix, StringComparison.Ordinal))
@@ -488,7 +487,7 @@ static async Task LoadSkillBundleAsync(DevFlowSkillDefinition skill
files.Add(new SkillAssetFile(relativePath, content, HashContent(content)));
}
- return new SkillBundle(skill.Id, GetCurrentCliVersion(), files, HashBundle(files));
+ return new SkillBundle(skill.Id, GetBundledSkillVersion(), files, HashBundle(files));
}
static bool ShouldExcludeSkillAsset(string relativePath)
@@ -1144,6 +1143,12 @@ static string GetCurrentCliVersion()
?? typeof(DevFlowSkillManager).Assembly.GetName().Version?.ToString()
?? "unknown";
+ static string GetBundledSkillVersion()
+ => MauiCliSkillResources.Assembly
+ .GetCustomAttribute()?.InformationalVersion
+ ?? MauiCliSkillResources.Assembly.GetName().Version?.ToString()
+ ?? GetCurrentCliVersion();
+
static string? GetString(JsonObject node, string propertyName)
=> node.TryGetPropertyValue(propertyName, out var value) ? value?.GetValue() : null;
diff --git a/src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj b/src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj
index 63765ef9d..cf0a03f4d 100644
--- a/src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj
+++ b/src/Cli/Microsoft.Maui.Cli/Microsoft.Maui.Cli.csproj
@@ -55,6 +55,7 @@
+
@@ -70,13 +71,4 @@
-
-
- false
-
-
- false
-
-
-