Add Aspire agent extensions to agent init - #19486
Add Aspire agent extensions to agent init#19486Ella Hathaway (ellahathaway) wants to merge 1 commit into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19486Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19486" |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Generalizes the CLI’s skills infrastructure into typed agent-asset models while preserving existing skills behavior.
Changes:
- Adds agent client, asset, file, and location abstractions.
- Makes bundle acquisition, manifests, caching, and telemetry descriptor-driven.
- Updates focused CLI tests for the generalized contracts.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tests/Aspire.Cli.Tests/TestServices/FakePlaywrightServices.cs |
Updates the fake bundle installer and manifest. |
tests/Aspire.Cli.Tests/Commands/InitCommandTests.cs |
Uses generalized asset locations. |
tests/Aspire.Cli.Tests/Commands/AgentInitCommandTests.cs |
Updates command, bundle, and client tests. |
tests/Aspire.Cli.Tests/Agents/TelemetryHookConfiguratorTests.cs |
Migrates telemetry tests to client definitions. |
tests/Aspire.Cli.Tests/Agents/CommonAgentApplicatorsTests.cs |
Verifies generalized locations and scopes. |
tests/Aspire.Cli.Tests/Agents/AspireSkillsInstallerTests.cs |
Updates acquisition tests and adds telemetry coverage. |
tests/Aspire.Cli.Tests/Agents/AspireSkillsBundleTests.cs |
Tests generalized assets and manifest mappings. |
src/Aspire.Cli/Telemetry/TelemetryConstants.cs |
Adds shared asset-bundle telemetry tags. |
src/Aspire.Cli/Projects/AppHostCandidateFinder.cs |
Updates the location reference. |
src/Aspire.Cli/Commands/InitCommand.cs |
Uses generalized asset locations. |
src/Aspire.Cli/Commands/AgentInitCommand.cs |
Migrates skill installation to asset abstractions. |
src/Aspire.Cli/Agents/VsCode/VsCodeAgentEnvironmentScanner.cs |
Reports the VS Code client definition. |
src/Aspire.Cli/Agents/SkillLocation.cs |
Removes the skill-specific location model. |
src/Aspire.Cli/Agents/SkillDefinition.cs |
Derives skills from the asset definition. |
src/Aspire.Cli/Agents/Playwright/PlaywrightCliInstaller.cs |
Uses asset locations for skill mirroring. |
src/Aspire.Cli/Agents/OpenCode/OpenCodeAgentEnvironmentScanner.cs |
Reports the OpenCode client definition. |
src/Aspire.Cli/Agents/Hooks/TelemetryHookConfigurator.cs |
Uses agent client definitions. |
src/Aspire.Cli/Agents/Hooks/ITelemetryHookConfigurator.cs |
Updates telemetry hook contracts. |
src/Aspire.Cli/Agents/CopilotCli/CopilotCliAgentEnvironmentScanner.cs |
Reports the Copilot CLI definition. |
src/Aspire.Cli/Agents/ClaudeCode/ClaudeCodeAgentEnvironmentScanner.cs |
Reports the Claude Code definition. |
src/Aspire.Cli/Agents/AspireSkills/TemporaryCacheDirectory.cs |
Generalizes cache documentation. |
src/Aspire.Cli/Agents/AspireSkills/SkillBundleManifest.cs |
Generalizes manifest assets. |
src/Aspire.Cli/Agents/AspireSkills/IAspireSkillsInstaller.cs |
Adds asset-kind selection. |
src/Aspire.Cli/Agents/AspireSkills/EmbeddedAspireSkillsBundleProvider.cs |
Resolves embedded bundles by descriptor. |
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsInstaller.cs |
Introduces descriptor-driven bundle installers. |
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleVersions.cs |
Centralizes the bundle version. |
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleProvider.cs |
Generalizes bundle loading and JSON contracts. |
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundleDescriptors.cs |
Defines the skills bundle descriptor. |
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsBundle.cs |
Exposes generalized asset definitions and files. |
src/Aspire.Cli/Agents/AgentEnvironmentScanContext.cs |
Tracks agent client definitions. |
src/Aspire.Cli/Agents/AgentClientKind.cs |
Removes the client enum. |
src/Aspire.Cli/Agents/AgentClient.cs |
Defines clients and supported asset kinds. |
src/Aspire.Cli/Agents/AgentAssetLocation.cs |
Adds typed locations and scopes. |
src/Aspire.Cli/Agents/AgentAssetKind.cs |
Defines agent asset kinds. |
src/Aspire.Cli/Agents/AgentAssetFile.cs |
Adds the generalized asset-file model. |
src/Aspire.Cli/Agents/AgentAssetDefinition.cs |
Adds the shared asset-definition base class. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 36 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Aspire.Cli/Agents/AspireSkills/AspireSkillsInstaller.cs:724
- Cached installations set
agent.asset_bundle.cache_hitbut never setagent.asset_bundle.source, so the new acquisition telemetry has no source on every successful cache path (both embedded and GitHub caches). Set the source before returning a cached result—either to a definedcachevalue or to persisted/derived original provenance—and cover a cached install in the telemetry test.
activity?.SetTag(TelemetryConstants.Tags.AgentAssetBundleCacheHit, true);
This comment has been minimized.
This comment has been minimized.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
This comment has been minimized.
This comment has been minimized.
c7daaa7 to
81e4ad9
Compare
This comment has been minimized.
This comment has been minimized.
81e4ad9 to
4e70efd
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 88 out of 90 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Aspire.Cli/Resources/AgentCommandStrings.Designer.cs: Generated file
Suppressed comments (1)
src/Aspire.Cli/Agents/AspireSkills/Embedded/aspire-extensions.metadata.json:5
- The referenced
microsoft/aspire-skillsv0.0.1 release currently publishes onlyaspire-skills-v0.0.1.tgz; this executable extension archive therefore has no matching release asset or attestation, so the new verification workflow cannot pass. Publish and attest the sibling extension asset, then rerun the plural updater to replace this archive/metadata before merging.
"assetName": "aspire-extensions-v0.0.1.tgz",
This comment has been minimized.
This comment has been minimized.
Resolves review findings from Claude Opus 5 (high) and GPT-5.6 Sol (high) on the selector rename-handling fix. Quoted-path parsing bug (GPT F2): `ResolveChangedFiles` parsed `git diff --name-status -M` output by splitting on literal tabs and newlines while only disabling non-ASCII quoting via `-c core.quotePath=false`. Git's default quoting still C-style-escapes tabs, newlines, quotes, and backslashes in paths, which would corrupt attribution for any changed path containing those bytes. Switched to `git diff --name-status -M -z`, which is NUL-terminated and never quotes/escapes any byte (NUL cannot appear in a valid path), and rewrote the parser to walk NUL-delimited tokens instead of splitting on tabs/newlines. Added a regression test using a same-commit rename with a literal tab byte in the path. Stale comments and test doc clarifications (Opus F2/F4, GPT F3, Opus F3/F5): - Corrected a comment in `RenameOutOfMappedPathStillSelectsItsTests` that still described the old `--no-renames` approach. - Added a clarifying note to `RenameBelowGitSimilarityThresholdIsNot- ExemptedAndStillForcesRunAll`'s docstring, cross-referencing the tests that actually fail on reversion of the exemption. - Added a docstring note on the golden `TestTriggerMapTests` case acknowledging its coupling to the live map is by design. - Wrapped a temp directory in try/finally with cleanup in `SameCommitRenameWithMapEntryMovedToNewPathSelectsExactTargets- WithoutEscalating`, matching the project's established convention. Residual risk (both reviewers, F1): git's `-M` rename detection is a content-similarity heuristic, not a semantic guarantee, so it can in rare cases mispair two unrelated files that happen to be highly similar. Raising the similarity threshold isn't viable — PR microsoft#19486's own motivating rename was only detected at R054. Documented this as an accepted, bounded limitation in both `TestSelector.cs` and `docs/ci/test-trigger-map.md` rather than attempting a fragile mitigation. Layer 1's `GraphAffectedProjects.cs` has an analogous quoted-path parsing gap, but it is untouched by this PR's diff and is left as a follow-up rather than expanded scope here. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
…a undetected rename Automated review on PR microsoft#19790 pointed out that the existing golden test only replayed 2 of the 3 files PR microsoft#19486 actually renamed, so it could pass while the PR's stated motivating scenario -- the literal PR microsoft#19486 diff -- still selects ALL today, for an undetected reason. That's true: `verify-aspire-skills-bundle.ps1` was rewritten heavily enough in the same commit that git's default -M50% similarity threshold reports it as a plain delete+add, not a rename, so this fix's exemption (keyed on git-detected renames) never applies to it. Reproduced directly against the real PR microsoft#19486 commits with the fixed selector (`--skip-layer1 --enforce`): the run still selects ALL, citing that file as the sole unattributed leftover. Add a companion golden test that replays PR microsoft#19486's complete three-path diff (not just the two git-detected renames) against the real production map, asserting the fallback still fires for the undetected file. This makes the existing, correct scope limitation explicit and falsifiable instead of only described in a comment. Also tightens the existing golden test's comment to point at the new test by name. No production code changes; this is test/documentation scope clarification only, following up on automated PR review feedback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
…afety gap An earlier commit on this branch exempted a rename's old path from the selector's run-all fallback when its new path was already present in the changed-file set. An audit found that exemption unsafe: a rename out of a directory-scoped glob (e.g. tests/Shared/Logging/**) can leave other consumers of that glob with no signal, and a rename whose new path is ignored or dropped by the prefilter satisfied the exemption's presence check without anything ever evaluating the destination. Both cases could silently under-select tests. Remove the exemption; renames are intentionally un-special-cased again, and an unmatched rename old path forces ALL like any other unaccounted-for change. Separately, while tracing this, Layer 1's git-diff parser (GraphAffectedProjects.GetChangedPathsFromGit) was found to still use git's default text-mode --name-status output, split on raw newline/tab bytes, while Layer 2 (Program.cs) already used a NUL-delimited -z parser. Git's text mode double-quotes and backslash-escapes any path containing a tab, newline, double quote, or backslash; -c core.quotePath=false only suppresses non-ASCII-byte escaping, not this class of path. A quoted/escaped path never matches Layer 1's evaluated-item index or directory-containment fallback, so it silently fails to attribute to its owning project -- an under-selection risk, not a fail-safe one. Layer 1 now issues the same `git diff --name-status -M -z` Layer 2 uses, and both layers parse it through one shared method (Selection.ParseNameStatusOutput) instead of two independently maintained parsers that had drifted. Net effect for renames: replaying PR microsoft#19486's exact diff still selects ALL today (the exemption that would have avoided that is gone). That is the accepted, safe tradeoff. The bug this commit actually fixes is the narrower NUL-safety gap above, found during the same investigation. Adds regression coverage under tests/Infrastructure.Tests/TestTriggerMap/ for: a tab-containing renamed path being correctly attributed instead of garbled (RenamedFileWithTabInPathIsAttributedCorrectlyNotGarbledByQuoting, CrossProjectRenameIntoTabContainingPathAttributesTheNewOwner, RenameIntoTabContainingPathIsAttributedByLayer1InsteadOfUnderSelecting); truncated -z input failing loudly instead of silently returning a partial set (ParseNameStatusOutputThrowsOnTruncatedRenameRecord, ParseNameStatusOutputThrowsOnTruncatedPlainRecord); and renames correctly forcing ALL with no special-casing (InPlaceRenameWithoutOwnMapEntryForcesRunAll, InPlaceRenameToPrefilteredDestinationForcesRunAll, RenameWhoseOldPathIncidentallyMatchesAnUnrelatedRuleStillAddsBothTargets, RenameOldPathUnmatchedForcesRunAllLikeAnyOtherLeftover, RenameWithBothUnmatchedPathsListsBothInUnmatchedFiles, RenameWhereBothPathsMatchTheirOwnRuleAddsTargetAdditively, SameCommitRenameWithMapEntryMovedToNewPathForcesRunAll -- the last a golden-scenario replay of PR microsoft#19486's real diff at head 1e46e48). docs/ci/test-trigger-map.md and docs/ci/test-trigger-selector-design.md are updated to describe the rename decision and the shared -z parser. Refs microsoft#19486 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7380ea02-bd9c-4638-a860-33cf32d59d46
4e70efd to
295c834
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 88 out of 90 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- src/Aspire.Cli/Resources/AgentCommandStrings.Designer.cs: Generated file
Suppressed comments (1)
src/Aspire.Cli/Agents/AspireSkills/Embedded/aspire-extensions.metadata.json:5
- The referenced
microsoft/aspire-skillsv0.0.1release currently contains onlyaspire-skills-v0.0.1.tgz; this Extension asset is not published there. The plural updater therefore cannot reproduce this embedded executable from the declared release, and runtime acquisition can never fetch it, violating the PR's requirement that both sibling assets be published together. Publish/attest the sibling asset first and regenerate these files with the updater, or reference a release that already contains it.
"assetName": "aspire-extensions-v0.0.1.tgz",
| var segments = normalizedPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); | ||
| if (segments.Length == 0 || segments.Any(static segment => !IsPortablePathSegment(segment))) | ||
| { | ||
| throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Aspire skills bundle path '{0}' is not safe.", relativePath)); | ||
| throw new InvalidOperationException($"{DisplayName} bundle path '{relativePath}' is not safe."); |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
This comment has been minimized.
This comment has been minimized.
| throw new InvalidOperationException($"Agent asset transaction directory '{transactionDirectoryPath}' changed unexpectedly."); | ||
| } | ||
|
|
||
| Directory.Delete(transactionDirectoryPath, recursive: true); |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ef2868ca-9135-456a-8efe-ff7ee7e426e0
Tests selectorSelects the full PR test matrix + all PR-gated jobs (ALL) — run-all fallback: 'eng/scripts/aspire-skills-bundle.common.ps1' is neither Layer-1-owned nor matched by a Layer 2 rule Advisory workflow impact (4)
Selection computed for commit |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
|
Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt. |
Description
aspire agent initcurrently installs only skills, even though the Aspire Skills repository publishes multiple agent-asset bundles. This change adds Extension support end to end so users can install theaspire-doctorcanvas extension at project or user scope without introducing a parallel acquisition pipeline.The implementation keeps
AgentAssetKindas the lightweight boundary between skills and extensions.AspireSkillsInstallerremains the product-level installer for artifacts frommicrosoft/aspire-skills, while singletonIAspireSkillsBundleProviderimplementations supply kind-specific metadata and validation. The shared abstract provider owns archive extraction, manifest loading, compatibility checks, embedded resources, hashing, and safe path validation.This PR:
.github/extensions/ user~/.copilot/extensionslocations;--extensionsand--extension-locationstoagent init,init, andnew;SkillBundleProviderandExtensionBundleProvideron the shared Aspire Skills provider path;aspire-extensions-v0.0.1.tgzcontainingaspire-doctorand requires normal GitHub release metadata and artifact attestation;Related to #19013.
User-facing usage
Install the Aspire Doctor extension for a project:
Install it for the current user:
The same extension options are also accepted by
aspire initandaspire new.Screenshots / Recordings
TODO: Capture the interactive Extension location and asset selection prompts from
aspire agent init.Security considerations
Extensions contain executable JavaScript that Copilot loads from project or user directories. Both Aspire Skills release archives must be published together and verified against the
microsoft/aspire-skillsrelease workflow attestation before their embedded snapshots are accepted. The runtime also validates archive containment, manifest paths, required entry files, and per-file hashes before installation.Validation:
Aspire.Cli.Testsbuilds with zero warnings and errors.v0.0.1, update/verification stops at the missing Extension release asset/attestation without modifying tracked files; this gate will pass after the sibling asset is published.Checklist
<remarks />and<code />elements on your triple slash comments?