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 - - -