Skip to content

Migrate shipped skills from embedded resources to microsoft/aspire-skills delegation #17018

@IEvangelist

Description

@IEvangelist

Migrate shipped skills from embedded resources to microsoft/aspire-skills delegation

This is a tracking issue (epic) for moving the two skills the Aspire CLI currently ships — aspire and aspireify — from MSBuild-embedded resources baked into the CLI binary to a delegated install from microsoft/aspire-skills, mirroring the existing pattern we already use for playwright-cli.

Scope note. This issue covers only the skills the CLI ships via aspire agent init (.agents/skills/aspire, .agents/skills/aspireify). It explicitly does not touch the contributor-facing skills under .github/skills/ (e.g., code-review, fix-flaky-test, backport-pr, api-review).

Problem

The Aspire CLI embeds two skills as managed resources inside the AOT-compiled binary:

  • .agents/skills/aspire/ — main Aspire CLI / AppHost workflow skill
  • .agents/skills/aspireify/ — one-time "complete Aspire initialization" skill

Pulled in via src/Aspire.Cli/Aspire.Cli.csproj:

<EmbeddedResource Include="..\..\.agents\skills\aspire\**\*"
                  LogicalName="skills.aspire/%(RecursiveDir)%(Filename)%(Extension)" />
<EmbeddedResource Include="..\..\.agents\skills\aspireify\**\*"
                  LogicalName="skills.aspireify/%(RecursiveDir)%(Filename)%(Extension)" />

…then surfaced via SkillDefinition.Aspire / SkillDefinition.Aspireify, loaded with EmbeddedSkillResourceLoader, and written to the workspace by AgentInitCommand at one of the four supported skill locations (.agents/skills, .claude/skills, .github/skills, .opencode/skill).

Pain points:

  • Source-of-truth drift. microsoft/aspire-skills already has parallel skills/aspire/ and skills/aspireify/ directories. Two source trees → manual sync risk and content rot.
  • Skill releases coupled to CLI releases. A user can't pick up an improved skill without a new CLI build.
  • Binary bloat in an AOT-compiled CLI. Skill markdown + reference files ship even for users who never run aspire agent init.
  • Inconsistent delivery model. PlaywrightCliInstaller and DotnetInspect already follow a delegated install pattern with npm + SLSA provenance verification. aspire and aspireify are the outliers.

Target state

microsoft/aspire-skills becomes the single, authoritative source for shipped Aspire skills. aspire agent init delegates install to an external installer, following the playbook we already use for playwright-cli, including:

  • Versioned, cached install under the global .aspire cache directory (the CliExecutionContext.CacheDirectory pattern)
  • Supply-chain verification (npm + SLSA provenance, matching PlaywrightCliInstaller)
  • Cleanup of stale cache entries (mirroring DiskCache MaxCacheAge)
  • Offline behavior parity with existing delegated skills — first install requires network; cache serves repeats

The primary delegation mechanism to evaluate is npx skills add microsoft/aspire-skills (or analog), shelled out exactly the way PlaywrightCliInstaller shells out today.

Current implementation map

Layer File Role today
Authoring .agents/skills/aspire/**, .agents/skills/aspireify/** Source markdown + references shipped in this repo
Build src/Aspire.Cli/Aspire.Cli.csproj EmbeddedResource Include="..\..\.agents\skills\..."
Catalog src/Aspire.Cli/Agents/SkillDefinition.cs Static list — Aspire, Aspireify, PlaywrightCli, DotnetInspect
Resource roots src/Aspire.Cli/Agents/CommonAgentApplicators.cs AspireSkillResourceRoot = "skills.aspire", AspireifySkillResourceRoot = "skills.aspireify"
Loader src/Aspire.Cli/Agents/EmbeddedSkillResourceLoader.cs Assembly.GetManifestResourceNames() + GetManifestResourceStream()
Locations src/Aspire.Cli/Agents/SkillLocation.cs Standard, ClaudeCode, GitHubSkills, OpenCode
Entry point src/Aspire.Cli/Commands/AgentInitCommand.cs Drives the install pipeline
Precedent (delegated) src/Aspire.Cli/Agents/Playwright/PlaywrightCliInstaller.cs npm + SLSA provenance pattern to mirror
Cache precedent src/Aspire.Cli/Caching/DiskCache.cs TTL + max-age cleanup pattern to mirror

Phased plan

Each phase below will be filed as its own follow-up issue. Check items as they are completed.

Phase 0 — Public-ize microsoft/aspire-skills (prerequisite)

  • Audit microsoft/aspire-skills for internal-only content (issue references, partner names, telemetry payloads, etc.)
  • Confirm LICENSE, SECURITY.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md are present and Microsoft-OSS compliant
  • Confirm GitHub Actions are safe to run from forks (no privileged secrets on PR triggers)
  • Flip repo visibility to public
  • Verify marketplace install instructions in the README still work after public-ization

Phase 1 — Establish microsoft/aspire-skills as the source of truth

  • Reconcile drift between microsoft/aspire/.agents/skills/{aspire,aspireify} and microsoft/aspire-skills/skills/{aspire,aspireify}; pick the authoritative copy per file and merge
  • Define a stable skill-manifest.json schema covering fields currently captured in SkillDefinition:
    • name, description, isDefault
    • applicableLanguages (optional)
    • installExcludedRelativePaths (so evals/ continues to be excluded from workspace install while still shipped in the bundle for QA)
    • files[] with relative paths + content hashes
  • Tag the repo with semver releases
  • Publish a release artifact format consumable by npx skills add (or whatever installer we settle on)
  • Add CI in microsoft/aspire-skills that publishes a tagged release bundle the CLI can pin against

Phase 2 — Design the external skill installer in the CLI

Mirror PlaywrightCliInstaller:

  • New abstraction (e.g., IExternalSkillInstaller with AspireSkillsInstaller implementation)
  • npm-based delegation (npx skills add microsoft/aspire-skills) — confirm tool availability or document a fallback
  • Supply-chain verification:
    • npm provenance check (reuse INpmProvenanceChecker)
    • SLSA build-type assertion (reuse ExpectedBuildType pattern)
    • Expected source repository pin (https://github.com/microsoft/aspire-skills)
    • Expected workflow path pin
  • Versioning:
    • VersionRange constant in the installer matching the PlaywrightCliInstaller.VersionRange style
    • CLI per-release pin recorded in source so a given CLI build resolves to a known-good skill version
  • Cache layout (under CliExecutionContext.CacheDirectory):
    • cache/aspire-skills/<package-version>/skills/{aspire,aspireify}/...
    • "current" pointer (file or symlink) for the version this CLI resolved last
  • Cleanup:
    • Reuse DiskCache-style MaxCacheAge (default 7 days for unused versions), configurable via IConfiguration
    • Always retain the version the running CLI is pinned to
  • Offline behavior parity with Playwright (first install needs network; cache serves repeats); document the gap in aspire agent init --help and a known-issues doc
  • AOT/trim safety review of the installer

Phase 3 — Wire the installer into AgentInitCommand / SkillDefinition

  • Change SkillDefinition.Aspire and SkillDefinition.Aspireify to mark them as externally sourced instead of carrying EmbeddedResourceRoot
  • Read per-skill metadata (description, installExcludedRelativePaths, isDefault, applicableLanguages) from the fetched manifest instead of hard-coded constants
  • AgentInitCommand flow:
    1. Detect environment + chosen skill locations (unchanged)
    2. Resolve target skill bundle version (pin from CLI)
    3. Hit cache → if miss/stale, invoke AspireSkillsInstaller
    4. Verify provenance + integrity
    5. Copy files from cache/aspire-skills/<version>/... into chosen SkillLocations, honoring installExcludedRelativePaths (e.g., evals/)
  • Keep EmbeddedSkillResourceLoader only as a one-release transition shim if needed; otherwise delete it

Phase 4 — Remove the embedded resources from microsoft/aspire

  • Delete .agents/skills/aspire/ and .agents/skills/aspireify/
  • Remove the two <EmbeddedResource Include="..\..\.agents\skills\..."> items from src/Aspire.Cli/Aspire.Cli.csproj
  • Delete src/Aspire.Cli/Agents/EmbeddedSkillResourceLoader.cs (and SkillAssetFile if no consumer remains)
  • Remove AspireSkillResourceRoot / AspireifySkillResourceRoot constants from CommonAgentApplicators.cs
  • Confirm SkillDefinition.All still surfaces the same four skills but two are now externally sourced

Phase 5 — Tests, telemetry, docs

  • Update AgentInitCommand integration tests to use a fake IExternalSkillInstaller + fixture cache instead of relying on GetManifestResourceStream
  • Tests for: cache hit, cache miss, stale cache, cleanup, provenance failure, npm-unavailable fallback
  • Add telemetry events (which skill, which version, fetch outcome, cache hit/miss) reusing AspireCliTelemetry
  • Update aspire agent init help text + Resources/AgentCommandStrings.resx + all .xlf translations to describe the network requirement for first install
  • Update any docs referencing .agents/skills/ as the canonical authoring location

Phase 6 — Version pinning and opt-in newer skills

  • Document how a CLI release pins an aspire-skills version
  • Document override mechanism (e.g., aspire agent init --skills-version <semver> or env var) for users who want to opt in to a newer skill bundle without waiting for the next CLI release
  • Document downgrade/pin-back path for users who hit regressions

Non-goals

  • Migrating contributor-facing skills under .github/skills/ (api-review, backport-pr, ci-test-failures, etc.) — those are repo-internal and stay embedded in microsoft/aspire
  • Changing how PlaywrightCliInstaller or DotnetInspect work (already externally sourced)
  • Building a generic third-party skill plugin marketplace inside the Aspire CLI
  • Localizing skill markdown content (orthogonal effort; if pursued, drive from microsoft/aspire-skills)
  • Replacing the four SkillLocation targets (.agents/skills, .claude/skills, .github/skills, .opencode/skill) — those remain the install destinations

Open questions

  1. Distribution channel — npm + npx skills add (matches Playwright precedent) vs GitHub release tarball vs dotnet tool. Recommend npm to reuse INpmRunner + INpmProvenanceChecker.
  2. npx skills tool authority — is skills an existing package we depend on, or do we need to publish a Microsoft-owned @microsoft/aspire-skills npm package and invoke it directly? Needs concrete confirmation before Phase 2 design lock.
  3. Manifest schema — JSON Schema-validated skill-manifest.json at the bundle root vs directory-walk convention. Recommend JSON Schema for forward-compat.
  4. Cache TTL defaults — reuse DiskCache defaults (3h expiry / 7d max age) or pick skill-specific values?
  5. Conflict handling — when an existing .agents/skills/aspire/ already exists in the workspace from a prior install, do we overwrite, prompt, or skip by default? Recommend prompt (current aspire agent init UX already prompts).
  6. Failure UX — what does the CLI do when the installer fails (npm missing, network down, provenance check fails)? Recommend a clear actionable error + link to docs; never silently fall back to a stale bundle without telling the user.
  7. CI dependency direction — does microsoft/aspire's CI need to pull microsoft/aspire-skills at build time to validate the pinned version exists/resolves? Likely yes.

Success criteria

  • microsoft/aspire-skills is public and consumable
  • aspire agent init installs aspire + aspireify skills via the external installer with verified provenance
  • Cached skill bundles live under the global Aspire cache directory and are aged out per policy
  • .agents/skills/aspire/ and .agents/skills/aspireify/ no longer exist in microsoft/aspire
  • The two <EmbeddedResource Include="..\..\.agents\skills\..."> lines are gone from Aspire.Cli.csproj
  • New + existing tests pass without relying on embedded skill resources
  • Telemetry surfaces which aspire-skills version each install resolved to

References

  • microsoft/aspire-skills: https://github.com/microsoft/aspire-skills
  • Playwright delegated install precedent: src/Aspire.Cli/Agents/Playwright/PlaywrightCliInstaller.cs
  • Cache precedent: src/Aspire.Cli/Caching/DiskCache.cs
  • Skill locations: src/Aspire.Cli/Agents/SkillLocation.cs
  • Skill catalog: src/Aspire.Cli/Agents/SkillDefinition.cs
  • aspire agent init entry: src/Aspire.Cli/Commands/AgentInitCommand.cs

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions