From 7a34de5a334153c9e87fb8add6bf003d4e0a5d3d Mon Sep 17 00:00:00 2001 From: hyoshis Date: Thu, 30 Jul 2026 11:21:53 -0700 Subject: [PATCH 01/10] Rename azd extension/tool upgrade commands to update Rename the user-facing 'azd extension upgrade' and 'azd tool upgrade' commands to 'update', keeping 'upgrade' as a cobra alias for backward compatibility. Update all displayed help text, flag descriptions, prompts, progress and summary messages, error strings, docs, and regenerate usage/figspec snapshots. Go identifiers, telemetry field-key constants, and the --no-dependency-upgrades flag name are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- cli/azd/CHANGELOG.md | 4 + cli/azd/cmd/extension.go | 101 +++++++++--------- cli/azd/cmd/extension_test.go | 12 +-- cli/azd/cmd/extension_upgrade_test.go | 2 +- cli/azd/cmd/testdata/TestFigSpec.ts | 25 +++-- ...ap => TestUsage-azd-extension-update.snap} | 16 +-- .../cmd/testdata/TestUsage-azd-extension.snap | 2 +- .../testdata/TestUsage-azd-tool-check.snap | 2 +- ...de.snap => TestUsage-azd-tool-update.snap} | 14 +-- cli/azd/cmd/testdata/TestUsage-azd-tool.snap | 4 +- cli/azd/cmd/tool.go | 63 +++++------ cli/azd/cmd/tool_test.go | 4 +- .../docs/extensions/extension-framework.md | 18 ++-- .../extension-resolution-and-versioning.md | 16 +-- docs/guides/creating-an-extension.md | 2 +- 15 files changed, 146 insertions(+), 139 deletions(-) rename cli/azd/cmd/testdata/{TestUsage-azd-extension-upgrade.snap => TestUsage-azd-extension-update.snap} (64%) rename cli/azd/cmd/testdata/{TestUsage-azd-tool-upgrade.snap => TestUsage-azd-tool-update.snap} (61%) diff --git a/cli/azd/CHANGELOG.md b/cli/azd/CHANGELOG.md index 6aba6c5601a..6b52a1f26f9 100644 --- a/cli/azd/CHANGELOG.md +++ b/cli/azd/CHANGELOG.md @@ -2,6 +2,10 @@ ## 1.30.0-beta.1 (Unreleased) +### Other Changes + +- Rename the `azd extension upgrade` and `azd tool upgrade` commands to `azd extension update` and `azd tool update`. The former `upgrade` names continue to work as aliases for backward compatibility. + ## 1.29.0 (2026-07-29) ### Features Added diff --git a/cli/azd/cmd/extension.go b/cli/azd/cmd/extension.go index b388933bda9..96602c4e1dd 100644 --- a/cli/azd/cmd/extension.go +++ b/cli/azd/cmd/extension.go @@ -114,32 +114,33 @@ tracked for updates; reinstall from a newer bundle to update.`, FlagsResolver: newExtensionUninstallFlags, }) - // azd extension upgrade - group.Add("upgrade", &actions.ActionDescriptorOptions{ + // azd extension update + group.Add("update", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ - Use: "upgrade [extension-id]", - Short: "Upgrade installed extensions to the latest version.", - Long: `Upgrade one or more installed extensions. + Use: "update [extension-id]", + Aliases: []string{"upgrade"}, + Short: "Update installed extensions to the latest version.", + Long: `Update one or more installed extensions. By default, uses the stored registry source for each extension. If the stored source is unavailable, falls back to the main (azd) registry. Extensions that were installed from a non-main registry (e.g., dev) are automatically promoted to the main registry when a newer version is available there. -Use --source to override the registry source for the upgrade. It accepts a +Use --source to override the registry source for the update. It accepts a registered source name or registry location (URL or file path); locations are -registered first and the upgraded extension's stored source is updated. Because +registered first and the updated extension's stored source is updated. Because registration is interactive, locations are rejected under --no-prompt. Use --all -to upgrade all installed extensions in a single batch; failures in one extension -do not prevent the remaining extensions from being upgraded. +to update all installed extensions in a single batch; failures in one extension +do not prevent the remaining extensions from being updated. -When upgrading an extension that has dependencies, any installed -dependencies are automatically upgraded too, to the highest version +When updating an extension that has dependencies, any installed +dependencies are automatically updated too, to the highest version satisfying the extension's declared constraints. Use ---no-dependency-upgrades to opt out and upgrade only the named +--no-dependency-upgrades to opt out and update only the named extension. -Use --output json for a structured report of all upgrade results.`, +Use --output json for a structured report of all update results.`, }, OutputFormats: []output.Format{output.JsonFormat, output.NoneFormat}, DefaultFormat: output.NoneFormat, @@ -169,7 +170,7 @@ Use --output json for a structured report of all upgrade results.`, Use: "add", Short: "Add an extension source with the specified name", Long: "Add an extension source with the specified name.\n\n" + - "`azd extension install --source` and `azd extension upgrade --source` also accept " + + "`azd extension install --source` and `azd extension update --source` also accept " + "a registry URL or file path directly.", }, ActionResolver: newExtensionSourceAddAction, @@ -484,9 +485,9 @@ func (a *extensionListAction) Run(ctx context.Context) (*actions.ActionResult, e if hasCompatibleUpdates { a.console.Message(ctx, fmt.Sprintf( - "To upgrade: %s", output.WithHighLightFormat("azd extension upgrade "))) + "To update: %s", output.WithHighLightFormat("azd extension update "))) a.console.Message(ctx, fmt.Sprintf( - "To upgrade all: %s", output.WithHighLightFormat("azd extension upgrade --all"))) + "To update all: %s", output.WithHighLightFormat("azd extension update --all"))) } if hasIncompatibleUpdates { @@ -507,7 +508,7 @@ func (a *extensionListAction) Run(ctx context.Context) (*actions.ActionResult, e // Status indicator constants for extension list display. const ( statusUpToDate = "Up to date" - statusUpgrade = "Upgrade available" + statusUpgrade = "Update available" statusIncompat = "Incompatible" statusNotInstall = "Not installed" ) @@ -1057,7 +1058,7 @@ func (a *extensionInstallAction) Run(ctx context.Context) (*actions.ActionResult ) if err != nil { a.console.StopSpinner(ctx, stepMessage, input.StepFailed) - return nil, wrapDependencyError(fmt.Errorf("failed to upgrade extension: %w", err)) + return nil, wrapDependencyError(fmt.Errorf("failed to update extension: %w", err)) } stepMessage += output.WithGrayFormat(" (%s)", extensionVersion.Version) @@ -1137,7 +1138,7 @@ func (a *extensionInstallAction) sourceDisplayLabelForInstalled(source string) s // versionTransitionVerb returns a capitalized verb phrase describing the move // from the installed version to the target version: "Reinstall" when they match, -// "Upgrade to " / "Downgrade to " when both parse as semver, and +// "Update to " / "Downgrade to " when both parse as semver, and // a neutral "Replace with " when ordering is undefined (non-semver tags). func versionTransitionVerb(installedVersion, targetVersion string) string { if installedVersion == targetVersion { @@ -1150,7 +1151,7 @@ func versionTransitionVerb(installedVersion, targetVersion string) string { case installedErr == nil && targetErr == nil && targetSemver.LessThan(installedSemver): return fmt.Sprintf("Downgrade to %s", targetVersion) case installedErr == nil && targetErr == nil && targetSemver.GreaterThan(installedSemver): - return fmt.Sprintf("Upgrade to %s", targetVersion) + return fmt.Sprintf("Update to %s", targetVersion) default: return fmt.Sprintf("Replace with %s", targetVersion) } @@ -1852,12 +1853,12 @@ func newExtensionUpgradeFlags(cmd *cobra.Command, global *internal.GlobalCommand flags := &extensionUpgradeFlags{ global: global, } - cmd.Flags().StringVarP(&flags.version, "version", "v", "", "The version of the extension to upgrade to") + cmd.Flags().StringVarP(&flags.version, "version", "v", "", "The version of the extension to update to") cmd.Flags().StringVarP(&flags.source, "source", "s", "", - "The registered source name or registry location (URL or file path) to use for upgrades.") - cmd.Flags().BoolVar(&flags.all, "all", false, "Upgrade all installed extensions") + "The registered source name or registry location (URL or file path) to use for updates.") + cmd.Flags().BoolVar(&flags.all, "all", false, "Update all installed extensions") cmd.Flags().BoolVar(&flags.noDependencyUpgrades, "no-dependency-upgrades", false, - "Do not upgrade dependencies when upgrading an extension that has dependencies") + "Do not update dependencies when updating an extension that has dependencies") return flags } @@ -1902,8 +1903,8 @@ func (a *extensionUpgradeAction) Run( Err: fmt.Errorf( "cannot specify both an extension name and --all flag: %w", internal.ErrInvalidFlagCombination), - Suggestion: "Use either 'azd extension upgrade ' " + - "or 'azd extension upgrade --all'.", + Suggestion: "Use either 'azd extension update ' " + + "or 'azd extension update --all'.", } } @@ -1912,7 +1913,7 @@ func (a *extensionUpgradeAction) Run( Err: fmt.Errorf( "cannot specify --version with multiple extensions: %w", internal.ErrInvalidFlagCombination), - Suggestion: "Upgrade one extension at a time when " + + Suggestion: "Update one extension at a time when " + "using --version.", } } @@ -1927,8 +1928,8 @@ func (a *extensionUpgradeAction) Run( if len(a.args) == 0 && !a.flags.all { return nil, &internal.ErrorWithSuggestion{ Err: internal.ErrNoArgsProvided, - Suggestion: "Run 'azd extension upgrade '" + - " or 'azd extension upgrade --all'.", + Suggestion: "Run 'azd extension update '" + + " or 'azd extension update --all'.", } } @@ -1936,9 +1937,9 @@ func (a *extensionUpgradeAction) Run( if !isJsonOutput { a.console.MessageUxItem(ctx, &ux.MessageTitle{ - Title: "Upgrade azd extensions " + - "(azd extension upgrade)", - TitleNote: "Upgrades the specified extensions " + + Title: "Update azd extensions " + + "(azd extension update)", + TitleNote: "Updates the specified extensions " + "on the local machine", }) } @@ -2010,7 +2011,7 @@ loop: report, a.writer, nil, ); err != nil { return nil, fmt.Errorf( - "failed to format upgrade report: %w", err, + "failed to format update report: %w", err, ) } return upgradeActionResult(results) @@ -2076,7 +2077,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( } stepMsg := fmt.Sprintf( - "Upgrading %s extension", + "Updating %s extension", output.WithHighLightFormat(extensionId), ) if !isJsonOutput { @@ -2098,7 +2099,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( a.console.Message(ctx, fmt.Sprintf( " Retry with: %s", output.WithHighLightFormat( - "azd extension upgrade %s", + "azd extension update %s", extensionId, ), )) @@ -2126,7 +2127,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( "reinstall with a newer bundle to update" if !isJsonOutput { skipMsg := fmt.Sprintf( - "Upgrading %s extension", + "Updating %s extension", output.WithHighLightFormat(extensionId), ) + output.WithGrayFormat( " (Installed from a bundle)", @@ -2170,7 +2171,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( "in any configured registry" if !isJsonOutput { skipMsg := fmt.Sprintf( - "Upgrading %s extension", + "Updating %s extension", output.WithHighLightFormat(extensionId), ) + output.WithGrayFormat( " (No longer available in any registry)", @@ -2308,7 +2309,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( baseResult.SkipReason = "already up to date" if !isJsonOutput { skipMsg := stepMsg + output.WithGrayFormat( - " (No upgrade available)", + " (No update available)", ) a.console.StopSpinner( ctx, skipMsg, input.StepSkipped, @@ -2329,13 +2330,13 @@ func (a *extensionUpgradeAction) upgradeOneExtension( if err != nil { if isNetworkError(err) { return fail(fmt.Errorf( - "network error upgrading %s "+ + "network error updating %s "+ "(check your connection and retry): %w", extensionId, err, )) } return fail(fmt.Errorf( - "failed to upgrade extension: %w", err, + "failed to update extension: %w", err, )) } baseResult.ToVersion = extVersion.Version @@ -2362,7 +2363,7 @@ func (a *extensionUpgradeAction) upgradeOneExtension( if !isJsonOutput { doneMsg := fmt.Sprintf( - "Upgraded %s extension %s", + "Updated %s extension %s", output.WithHighLightFormat(extensionId), output.WithGrayFormat( "(%s \u2192 %s)", @@ -2392,7 +2393,7 @@ func (a *extensionUpgradeAction) displayPromotionWarning( ) { a.console.StopSpinner(ctx, stepMsg, input.StepWarning) a.console.Message(ctx, output.WithWarningFormat( - " (!) Warning: Upgraded %s extension (%s \u2192 %s, %s \u2192 %s registry)", + " (!) Warning: Updated %s extension (%s \u2192 %s, %s \u2192 %s registry)", output.WithHighLightFormat(extensionId), fromVersion, toVersion, output.WithHighLightFormat(oldSource), @@ -2442,7 +2443,7 @@ func displayDependencyUpgradeResults( )) case extensions.UpgradeStatusFailed: console.Message(ctx, fmt.Sprintf( - "%s%s Upgrading %s dependency%s", + "%s%s Updating %s dependency%s", indent, output.WithErrorFormat("(x) Failed:"), output.WithHighLightFormat(child.ExtensionId), @@ -2457,7 +2458,7 @@ func displayDependencyUpgradeResults( )) case extensions.UpgradeStatusSkipped: line := fmt.Sprintf( - "%s%s Upgrading %s dependency", + "%s%s Updating %s dependency", indent, output.WithGrayFormat("(-) Skipped:"), output.WithHighLightFormat(child.ExtensionId), @@ -2488,7 +2489,7 @@ func dependencyChangeVerb(fromVersion, toVersion string) string { if to.LessThan(from) { return "Downgraded" } - return "Upgraded" + return "Updated" } // displayUpgradeSummary prints the batch summary line after all @@ -2505,7 +2506,7 @@ func displayUpgradeSummary( depUpgraded := summary.DependencyUpgradesByStatus[extensions.UpgradeStatusUpgraded] if summary.Upgraded > 0 { upgradedPart := output.WithSuccessFormat( - "%d upgraded", summary.Upgraded, + "%d updated", summary.Upgraded, ) if depUpgraded > 0 { noun := "dependency" @@ -2551,7 +2552,7 @@ func displayUpgradeSummary( console.Message(ctx, fmt.Sprintf( " Run '%s' to retry failed extensions.", output.WithHighLightFormat( - "azd extension upgrade ", + "azd extension update ", ), )) } @@ -2568,19 +2569,19 @@ func upgradeActionResult( return &actions.ActionResult{ Message: &actions.ResultMessage{ Header: fmt.Sprintf( - "%d of %d extensions failed to upgrade", + "%d of %d extensions failed to update", summary.Failed, summary.Total, ), }, }, fmt.Errorf( - "%d of %d extensions failed to upgrade", + "%d of %d extensions failed to update", summary.Failed, summary.Total, ) } return &actions.ActionResult{ Message: &actions.ResultMessage{ - Header: "Extensions upgraded successfully", + Header: "Extensions updated successfully", }, }, nil } diff --git a/cli/azd/cmd/extension_test.go b/cli/azd/cmd/extension_test.go index 914557eef6d..743f97abdb4 100644 --- a/cli/azd/cmd/extension_test.go +++ b/cli/azd/cmd/extension_test.go @@ -494,7 +494,7 @@ func TestDisplayUpgradeSummary(t *testing.T) { {Status: extensions.UpgradeStatusUpgraded}, }, wantMsgs: []string{ - "2 upgraded", + "2 updated", }, }, { @@ -506,7 +506,7 @@ func TestDisplayUpgradeSummary(t *testing.T) { {Status: extensions.UpgradeStatusFailed}, }, wantMsgs: []string{ - "1 upgraded", + "1 updated", "1 skipped", "1 promoted", "1 failed", @@ -519,7 +519,7 @@ func TestDisplayUpgradeSummary(t *testing.T) { }, wantMsgs: []string{ "1 failed", - "azd extension upgrade ", + "azd extension update ", }, }, { @@ -590,7 +590,7 @@ func TestUpgradeActionResult(t *testing.T) { require.NotNil(t, actionResult) assert.Equal( t, - "Extensions upgraded successfully", + "Extensions updated successfully", actionResult.Message.Header, ) }) @@ -609,7 +609,7 @@ func TestUpgradeActionResult(t *testing.T) { require.NotNil(t, actionResult) assert.Contains( t, err.Error(), - "2 of 3 extensions failed to upgrade", + "2 of 3 extensions failed to update", ) assert.Contains( t, actionResult.Message.Header, @@ -643,7 +643,7 @@ func TestUpgradeActionResult_EmptyResults(t *testing.T) { require.NotNil(t, actionResult) assert.Equal( t, - "Extensions upgraded successfully", + "Extensions updated successfully", actionResult.Message.Header, ) } diff --git a/cli/azd/cmd/extension_upgrade_test.go b/cli/azd/cmd/extension_upgrade_test.go index ee7110113fe..207de335f61 100644 --- a/cli/azd/cmd/extension_upgrade_test.go +++ b/cli/azd/cmd/extension_upgrade_test.go @@ -147,7 +147,7 @@ func TestUpgradeAction_ContextCancellation(t *testing.T) { // All extensions should be marked as failed require.Error(t, err) require.NotNil(t, result) - assert.Contains(t, err.Error(), "extensions failed to upgrade") + assert.Contains(t, err.Error(), "extensions failed to update") // Parse the JSON output to verify all have failed status var report struct { diff --git a/cli/azd/cmd/testdata/TestFigSpec.ts b/cli/azd/cmd/testdata/TestFigSpec.ts index 373a5ff2945..dfc0d91c948 100644 --- a/cli/azd/cmd/testdata/TestFigSpec.ts +++ b/cli/azd/cmd/testdata/TestFigSpec.ts @@ -6009,20 +6009,20 @@ const completionSpec: Fig.Spec = { }, }, { - name: ['upgrade'], - description: 'Upgrade installed extensions to the latest version.', + name: ['update', 'upgrade'], + description: 'Update installed extensions to the latest version.', options: [ { name: ['--all'], - description: 'Upgrade all installed extensions', + description: 'Update all installed extensions', }, { name: ['--no-dependency-upgrades'], - description: 'Do not upgrade dependencies when upgrading an extension that has dependencies', + description: 'Do not update dependencies when updating an extension that has dependencies', }, { name: ['--source', '-s'], - description: 'The registered source name or registry location (URL or file path) to use for upgrades.', + description: 'The registered source name or registry location (URL or file path) to use for updates.', args: [ { name: 'source', @@ -6031,7 +6031,7 @@ const completionSpec: Fig.Spec = { }, { name: ['--version', '-v'], - description: 'The version of the extension to upgrade to', + description: 'The version of the extension to update to', args: [ { name: 'version', @@ -6042,7 +6042,6 @@ const completionSpec: Fig.Spec = { args: { name: 'extension-id', isOptional: true, - generators: azdGenerators.listInstalledExtensions, }, }, ], @@ -6515,7 +6514,7 @@ const completionSpec: Fig.Spec = { subcommands: [ { name: ['check'], - description: 'Check for tool upgrades.', + description: 'Check for tool updates.', }, { name: ['install'], @@ -6585,12 +6584,12 @@ const completionSpec: Fig.Spec = { }, }, { - name: ['upgrade'], - description: 'Upgrade installed tools.', + name: ['update', 'upgrade'], + description: 'Update installed tools.', options: [ { name: ['--agent'], - description: 'Upgrade the skill for the specified agent(s): copilot, claude. Use --agent all for every detected agent (skill tools only)', + description: 'Update the skill for the specified agent(s): copilot, claude. Use --agent all for every detected agent (skill tools only)', isRepeatable: true, args: [ { @@ -6600,11 +6599,11 @@ const completionSpec: Fig.Spec = { }, { name: ['--all'], - description: 'Upgrade all installed tools', + description: 'Update all installed tools', }, { name: ['--dry-run'], - description: 'Preview what would be upgraded without making changes', + description: 'Preview what would be updated without making changes', }, ], args: { diff --git a/cli/azd/cmd/testdata/TestUsage-azd-extension-upgrade.snap b/cli/azd/cmd/testdata/TestUsage-azd-extension-update.snap similarity index 64% rename from cli/azd/cmd/testdata/TestUsage-azd-extension-upgrade.snap rename to cli/azd/cmd/testdata/TestUsage-azd-extension-update.snap index d041b100383..9689ef97559 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-extension-upgrade.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-extension-update.snap @@ -1,21 +1,21 @@ -Upgrade installed extensions to the latest version. +Update installed extensions to the latest version. Usage - azd extension upgrade [extension-id] [flags] + azd extension update [extension-id] [flags] Flags - --all : Upgrade all installed extensions - --no-dependency-upgrades : Do not upgrade dependencies when upgrading an extension that has dependencies - -s, --source string : The registered source name or registry location (URL or file path) to use for upgrades. - -v, --version string : The version of the extension to upgrade to + --all : Update all installed extensions + --no-dependency-upgrades : Do not update dependencies when updating an extension that has dependencies + -s, --source string : The registered source name or registry location (URL or file path) to use for updates. + -v, --version string : The version of the extension to update to Global Flags -C, --cwd string : Sets the current working directory. --debug : Enables debugging and diagnostics logging. - --docs : Opens the documentation for azd extension upgrade in your web browser. + --docs : Opens the documentation for azd extension update in your web browser. -e, --environment string : The name of the environment to use. - -h, --help : Gets help for upgrade. + -h, --help : Gets help for update. --no-prompt : Runs without prompts. Uses existing values; fails if any required value or decision cannot be resolved automatically. Automatically enabled when azd detects a CI/CD or AI-agent environment; set AZD_NON_INTERACTIVE=false to opt out of that automatic enablement. Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-extension.snap b/cli/azd/cmd/testdata/TestUsage-azd-extension.snap index 6a42e9cc5d4..42e82948f3a 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-extension.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-extension.snap @@ -10,7 +10,7 @@ Available Commands show : Show details for a specific extension. source : View and manage extension sources uninstall : Uninstall specified extensions. - upgrade : Upgrade installed extensions to the latest version. + update : Update installed extensions to the latest version. Global Flags -C, --cwd string : Sets the current working directory. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-tool-check.snap b/cli/azd/cmd/testdata/TestUsage-azd-tool-check.snap index 219b2328cc6..a9a05d9ded0 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-tool-check.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-tool-check.snap @@ -1,5 +1,5 @@ -Check for tool upgrades. +Check for tool updates. Usage azd tool check [flags] diff --git a/cli/azd/cmd/testdata/TestUsage-azd-tool-upgrade.snap b/cli/azd/cmd/testdata/TestUsage-azd-tool-update.snap similarity index 61% rename from cli/azd/cmd/testdata/TestUsage-azd-tool-upgrade.snap rename to cli/azd/cmd/testdata/TestUsage-azd-tool-update.snap index d5d6bed8209..7cf99619396 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-tool-upgrade.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-tool-update.snap @@ -1,20 +1,20 @@ -Upgrade installed tools. +Update installed tools. Usage - azd tool upgrade [tool-name...] [flags] + azd tool update [tool-name...] [flags] Flags - --agent strings : Upgrade the skill for the specified agent(s): copilot, claude. Use --agent all for every detected agent (skill tools only) - --all : Upgrade all installed tools - --dry-run : Preview what would be upgraded without making changes + --agent strings : Update the skill for the specified agent(s): copilot, claude. Use --agent all for every detected agent (skill tools only) + --all : Update all installed tools + --dry-run : Preview what would be updated without making changes Global Flags -C, --cwd string : Sets the current working directory. --debug : Enables debugging and diagnostics logging. - --docs : Opens the documentation for azd tool upgrade in your web browser. + --docs : Opens the documentation for azd tool update in your web browser. -e, --environment string : The name of the environment to use. - -h, --help : Gets help for upgrade. + -h, --help : Gets help for update. --no-prompt : Runs without prompts. Uses existing values; fails if any required value or decision cannot be resolved automatically. Automatically enabled when azd detects a CI/CD or AI-agent environment; set AZD_NON_INTERACTIVE=false to opt out of that automatic enablement. Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-tool.snap b/cli/azd/cmd/testdata/TestUsage-azd-tool.snap index 1f201972839..d7b8a2cad72 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-tool.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-tool.snap @@ -5,12 +5,12 @@ Usage azd tool [command] Available Commands - check : Check for tool upgrades. + check : Check for tool updates. install : Install specified tools. list : List all tools with status. show : Show details for a specific tool. uninstall : Uninstall installed tools. - upgrade : Upgrade installed tools. + update : Update installed tools. Global Flags -C, --cwd string : Sets the current working directory. diff --git a/cli/azd/cmd/tool.go b/cli/azd/cmd/tool.go index ac668528fdb..671bd7c4df1 100644 --- a/cli/azd/cmd/tool.go +++ b/cli/azd/cmd/tool.go @@ -93,7 +93,7 @@ func toolActions(root *actions.ActionDescriptor) *actions.ActionDescriptor { toolCmd := &cobra.Command{ Use: "tool", Short: "Manage Azure development tools.", - Long: "Discover, install, upgrade, and check status of Azure development tools.", + Long: "Discover, install, update, and check status of Azure development tools.", } group := root.Add("tool", &actions.ActionDescriptorOptions{ @@ -127,11 +127,12 @@ func toolActions(root *actions.ActionDescriptor) *actions.ActionDescriptor { FlagsResolver: newToolInstallFlags, }) - // azd tool upgrade [tool-name...] - group.Add("upgrade", &actions.ActionDescriptorOptions{ + // azd tool update [tool-name...] + group.Add("update", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ - Use: "upgrade [tool-name...]", - Short: "Upgrade installed tools.", + Use: "update [tool-name...]", + Aliases: []string{"upgrade"}, + Short: "Update installed tools.", }, OutputFormats: []output.Format{output.JsonFormat, output.NoneFormat}, DefaultFormat: output.NoneFormat, @@ -155,7 +156,7 @@ func toolActions(root *actions.ActionDescriptor) *actions.ActionDescriptor { group.Add("check", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Use: "check", - Short: "Check for tool upgrades.", + Short: "Check for tool updates.", }, OutputFormats: []output.Format{output.JsonFormat, output.TableFormat}, DefaultFormat: output.TableFormat, @@ -1174,15 +1175,15 @@ func newToolUpgradeFlags(cmd *cobra.Command) *toolUpgradeFlags { flags := &toolUpgradeFlags{} cmd.Flags().BoolVar( &flags.all, "all", false, - "Upgrade all installed tools", + "Update all installed tools", ) cmd.Flags().BoolVar( &flags.dryRun, "dry-run", false, - "Preview what would be upgraded without making changes", + "Preview what would be updated without making changes", ) cmd.Flags().StringSliceVar( &flags.agents, "agent", nil, - "Upgrade the skill for the specified agent(s): copilot, claude. "+ + "Update the skill for the specified agent(s): copilot, claude. "+ "Use --agent all for every detected agent (skill tools only)", ) return flags @@ -1230,7 +1231,7 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err fromVersions := make(map[string]string) if len(a.args) > 0 && a.flags.all { - return nil, toolIDsWithAllError("upgrade") + return nil, toolIDsWithAllError("update") } switch { @@ -1278,7 +1279,7 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err // can't run (or would corrupt JSON), so require an explicit target // (tool IDs or --all) rather than implicitly upgrading every tool. if len(installed) > 0 && !promptAllowed(a.console, a.formatter) { - return nil, noToolTargetError("upgrade") + return nil, noToolTargetError("update") } chosen := installed if promptAllowed(a.console, a.formatter) && len(installed) > 0 { @@ -1302,7 +1303,7 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err return nil, a.formatter.Format([]*toolInstallResultItem{}, a.writer, nil) } a.console.Message(ctx, output.WithGrayFormat( - "No installed tools to upgrade.", + "No installed tools to update.", )) return nil, nil } @@ -1323,8 +1324,8 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err if a.formatter.Kind() != output.JsonFormat { a.console.MessageUxItem(ctx, &ux.MessageTitle{ - Title: "Upgrade Azure development tools (azd tool upgrade)", - TitleNote: "Upgrades installed tools to their latest versions", + Title: "Update Azure development tools (azd tool update)", + TitleNote: "Updates installed tools to their latest versions", }) } @@ -1358,7 +1359,7 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err operationFn := func(ctx context.Context, allIDs []string) ([]*tool.InstallResult, error) { return a.manager.UpgradeTools(ctx, allIDs, agentOpts...) } - outcome := runToolOperation(ctx, toolsToUpgrade, operationFn, "Upgrading", "upgrade", a.console, + outcome := runToolOperation(ctx, toolsToUpgrade, operationFn, "Updating", "update", a.console, a.formatter.Kind() == output.JsonFormat) upgradeResults = outcome.Items rawResults = outcome.Results @@ -1414,25 +1415,25 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err } } - header := "Tool is upgraded." + header := "Tool is updated." if allUpToDate { header = "Tool is already up to date." } if len(rawResults) > 1 { - header = "Tools are upgraded." + header = "Tools are updated." if allUpToDate { header = "Tools are already up to date." } } // For a single tool, include the resulting version in the done message, - // e.g. "Tool is upgraded to v2.0.0." or + // e.g. "Tool is updated to v2.0.0." or // "Tool is already up to date (v1.1.75).". if len(rawResults) == 1 && rawResults[0].InstalledVersion != "" { version := rawResults[0].InstalledVersion if allUpToDate { header = fmt.Sprintf("Tool is already up to date (v%s).", version) } else { - header = fmt.Sprintf("Tool is upgraded to v%s.", version) + header = fmt.Sprintf("Tool is updated to v%s.", version) } } @@ -1472,7 +1473,7 @@ func (a *toolUpgradeAction) promptForUpgradeTools( multiSelect := uxlib.NewMultiSelect(&uxlib.MultiSelectOptions{ Writer: a.console.Handles().Stdout, Reader: a.console.Handles().Stdin, - Message: "Select tools to upgrade", + Message: "Select tools to update", Choices: choices, }) @@ -1533,7 +1534,7 @@ func (a *toolUpgradeAction) dryRun( ) } - action := "upgrade" + action := "update" currentVersion := "" if status.Installed { currentVersion = status.InstalledVersion @@ -1925,7 +1926,7 @@ func (a *toolCheckAction) Run(ctx context.Context) (*actions.ActionResult, error var results []*tool.UpdateCheckResult if a.formatter.Kind() != output.JsonFormat { spinner := uxlib.NewSpinner(&uxlib.SpinnerOptions{ - Text: "Checking for upgrades...", + Text: "Checking for updates...", ClearOnStop: true, Writer: a.writer, }) @@ -1934,13 +1935,13 @@ func (a *toolCheckAction) Run(ctx context.Context) (*actions.ActionResult, error results, detectErr = a.manager.CheckForUpdates(ctx) return detectErr }); err != nil { - return nil, fmt.Errorf("checking for upgrades: %w", err) + return nil, fmt.Errorf("checking for updates: %w", err) } } else { var err error results, err = a.manager.CheckForUpdates(ctx) if err != nil { - return nil, fmt.Errorf("checking for upgrades: %w", err) + return nil, fmt.Errorf("checking for updates: %w", err) } } @@ -2049,12 +2050,12 @@ func (a *toolCheckAction) Run(ctx context.Context) (*actions.ActionResult, error if hasUpdates { a.console.Message(ctx, "") a.console.Message(ctx, fmt.Sprintf( - "To upgrade: %s", - output.WithHighLightFormat("azd tool upgrade "), + "To update: %s", + output.WithHighLightFormat("azd tool update "), )) a.console.Message(ctx, fmt.Sprintf( - "To upgrade all: %s", - output.WithHighLightFormat("azd tool upgrade --all"), + "To update all: %s", + output.WithHighLightFormat("azd tool update --all"), )) } } @@ -2355,8 +2356,8 @@ type toolOpOutcome struct { // Parameters: // - tools: the resolved ToolDefinition slice to operate on // - operationFn: either InstallTools or UpgradeTools -// - title: verb for task titles (e.g. "Installing", "Upgrading") -// - action: action label for result items (e.g. "install", "upgrade") +// - title: verb for task titles (e.g. "Installing", "Updating") +// - action: action label for result items (e.g. "install", "update") // - console: for displaying warnings on partial failure // - quiet: when true (JSON output) the per-tool TaskList is routed to // io.Discard so its progress/control bytes never corrupt the @@ -2516,7 +2517,7 @@ func runToolOperation( taskErr := taskList.Run() if taskErr != nil && !quiet { // Build the past participle: "install" -> "installed", - // "upgrade" -> "upgraded". Appending only "d" would be wrong, + // "update" -> "updated". Appending only "d" would be wrong, // so append "ed" unless the verb already ends in "e". participle := action + "ed" if strings.HasSuffix(action, "e") { diff --git a/cli/azd/cmd/tool_test.go b/cli/azd/cmd/tool_test.go index fd579b40f12..37de5c0d11d 100644 --- a/cli/azd/cmd/tool_test.go +++ b/cli/azd/cmd/tool_test.go @@ -1705,7 +1705,7 @@ func TestToolUpgradeAction_ChangedVersion_ReportsUpgraded(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Message) - assert.Equal(t, "Tool is upgraded to v2.0.0.", result.Message.Header) + assert.Equal(t, "Tool is updated to v2.0.0.", result.Message.Header) } // TestSkillAgentDisplayName verifies an installed agent's command identity is @@ -2004,7 +2004,7 @@ func TestToolUpgradeAction_MultiAgentSkill_UpgradedNotUpToDate(t *testing.T) { require.NoError(t, err) require.NotNil(t, result) require.NotNil(t, result.Message) - assert.Equal(t, "Tool is upgraded to v1.1.87.", result.Message.Header, + assert.Equal(t, "Tool is updated to v1.1.87.", result.Message.Header, "a multi-agent skill with an upgraded agent must not read as already up to date") } diff --git a/cli/azd/docs/extensions/extension-framework.md b/cli/azd/docs/extensions/extension-framework.md index 60f550d995f..7f7c41f12a3 100644 --- a/cli/azd/docs/extensions/extension-framework.md +++ b/cli/azd/docs/extensions/extension-framework.md @@ -148,14 +148,16 @@ Uninstalls one or more previously installed extensions. - `--all` Removes all installed extensions when specified. -#### `azd extension upgrade ` +#### `azd extension update ` -Upgrades one or more extensions to the latest versions. +> Aliased as `azd extension upgrade` for backward compatibility. -- `--all` Upgrades all previously installed extensions when specified. -- `-v, --version` Upgrades a specified extension to an exact version, if provided. -- `-s, --source` Specifies the source used for the upgrade. In addition to registered source names, this accepts a registry location (URL or file path). `azd` registers the location as a source before resolving the extension, updates the extension's stored source after a successful upgrade, and rejects locations under `--no-prompt`; add the source first with `azd extension source add`. -- `--no-dependency-upgrades` Skips upgrading dependencies declared by extension packs. +Updates one or more extensions to the latest versions. + +- `--all` Updates all previously installed extensions when specified. +- `-v, --version` Updates a specified extension to an exact version, if provided. +- `-s, --source` Specifies the source used for the update. In addition to registered source names, this accepts a registry location (URL or file path). `azd` registers the location as a source before resolving the extension, updates the extension's stored source after a successful update, and rejects locations under `--no-prompt`; add the source first with `azd extension source add`. +- `--no-dependency-upgrades` Skips updating dependencies declared by extension packs. ## Developing Extensions @@ -1209,9 +1211,9 @@ dependencies: version: "~0.1.0-preview" ``` -Pack manifests must include at least one dependency. They may omit `capabilities`, `namespace`, `entryPoint`, `usage`, and `examples` when the pack has no commands of its own. Installing a pack installs its dependencies recursively from the same extension source as the pack. Dependency versions in the manifest support semver constraints, but command-line `--version` values for `azd extension install` and `azd extension upgrade` are exact versions. +Pack manifests must include at least one dependency. They may omit `capabilities`, `namespace`, `entryPoint`, `usage`, and `examples` when the pack has no commands of its own. Installing a pack installs its dependencies recursively from the same extension source as the pack. Dependency versions in the manifest support semver constraints, but command-line `--version` values for `azd extension install` and `azd extension update` are exact versions. -Upgrading a pack upgrades the pack and, by default, reconciles installed dependencies to the highest published versions that satisfy the pack's declared dependency constraints. This dependency reconciliation still runs when the pack itself is already current, because an unchanged pack can point to a dependency range with newer matching versions. Users can disable automatic dependency upgrades with `azd extension upgrade --no-dependency-upgrades`. +Updating a pack updates the pack and, by default, reconciles installed dependencies to the highest published versions that satisfy the pack's declared dependency constraints. This dependency reconciliation still runs when the pack itself is already current, because an unchanged pack can point to a dependency range with newer matching versions. Users can disable automatic dependency updates with `azd extension update --no-dependency-upgrades`. #### Provider Registration diff --git a/cli/azd/docs/extensions/extension-resolution-and-versioning.md b/cli/azd/docs/extensions/extension-resolution-and-versioning.md index 3fbb0c1a673..6d9c516e35f 100644 --- a/cli/azd/docs/extensions/extension-resolution-and-versioning.md +++ b/cli/azd/docs/extensions/extension-resolution-and-versioning.md @@ -13,7 +13,7 @@ Extension sources are manifests that describe the extensions available for insta | `url` | HTTP/HTTPS endpoint | Remote JSON manifest fetched over the network. | | `file` | Local filesystem path | Local JSON file, useful for development and offline scenarios. | -In addition, extensions installed from a [self-contained bundle](#self-contained-bundles) are tagged with a reserved `bundle` source. `bundle` is not a configurable source type and never appears in `azd extension source list` — it simply marks an extension that has no live registry to track updates against. Such extensions are listed with their `bundle` source in `azd extension list` and are skipped by `azd extension upgrade`. The name `bundle` is reserved, so it cannot be used as a user-configured source name. +In addition, extensions installed from a [self-contained bundle](#self-contained-bundles) are tagged with a reserved `bundle` source. `bundle` is not a configurable source type and never appears in `azd extension source list` — it simply marks an extension that has no live registry to track updates against. Such extensions are listed with their `bundle` source in `azd extension list` and are skipped by `azd extension update`. The name `bundle` is reserved, so it cannot be used as a user-configured source name. Sources are configured in `~/.azd/config.json`. You can manage them with the following commands: @@ -209,7 +209,7 @@ The install flow treats the bundle as an **installer, not a registry** — nothi Because a bundle does not register a lasting source, a bundle-installed extension is tracked under the reserved `bundle` source: - `azd extension list` shows it with its `bundle` source and a normal `✓ Up to date` status. It has no "latest" version to compare against, so no update is ever reported. -- `azd extension upgrade` skips bundle-installed extensions with a note that they were installed from a self-contained bundle. +- `azd extension update` skips bundle-installed extensions with a note that they were installed from a self-contained bundle. - `azd extension source list` does **not** show an entry for the bundle — there is no leftover source to clean up. To update a bundle-installed extension, install a newer bundle: @@ -483,7 +483,7 @@ If an extension exists in both the `azd` and `dev` sources and you do not specif ### Upgrade and Dev→Main Promotion -When you run `azd extension upgrade`, extensions installed from the dev registry are evaluated for **one-way promotion** to the main registry. Promotion occurs automatically when: +When you run `azd extension update`, extensions installed from the dev registry are evaluated for **one-way promotion** to the main registry. Promotion occurs automatically when: 1. **The extension is no longer in the dev registry** — it was removed from `registry.dev.json` after being promoted to `registry.json`. 2. **The main registry has a newer version** — the latest version in the main registry is strictly greater than the latest version in the dev registry. @@ -508,9 +508,9 @@ Promotion events are tracked via `ext.promote` telemetry. Upgrade events (regard azd extension install my.extension --source dev # Later, the extension graduates to the main registry with a newer version. -# Running upgrade will auto-promote: -azd extension upgrade my.extension -# Output: my.extension upgraded from 1.0.0-beta.2 (dev) → 1.0.0 (azd) +# Running update will auto-promote: +azd extension update my.extension +# Output: my.extension updated from 1.0.0-beta.2 (dev) → 1.0.0 (azd) ``` ### Submitting an Extension to the Dev Registry @@ -682,9 +682,9 @@ azd extension source remove nightly ### Upgrade and Nightly→Main Promotion -Nightly versions use semver prerelease labels, so the standard `azd extension upgrade` flow works: +Nightly versions use semver prerelease labels, so the standard `azd extension update` flow works: -- A newer nightly (higher build id, or a higher base version) supersedes an older one, so `azd extension upgrade` pulls the latest nightly. +- A newer nightly (higher build id, or a higher base version) supersedes an older one, so `azd extension update` pulls the latest nightly. - When the extension ships a **stable** release whose base version matches your nightly (for example stable `1.2.3` versus `1.2.3-nightly.200`), the stable release outranks the nightly and you are **automatically promoted** to the `azd` registry on your next upgrade. > [!NOTE] diff --git a/docs/guides/creating-an-extension.md b/docs/guides/creating-an-extension.md index c47d92ee047..0b8b2a2ec03 100644 --- a/docs/guides/creating-an-extension.md +++ b/docs/guides/creating-an-extension.md @@ -76,7 +76,7 @@ For extensions that are still in development or preview, consider publishing to azd extension install my.extension --source dev ``` -4. Once your extension is stable and meets the quality bar, submit a follow-up PR to add it to `cli/azd/extensions/registry.json`. Users who installed from the dev registry will be **automatically promoted** to the main registry on their next `azd extension upgrade`. +4. Once your extension is stable and meets the quality bar, submit a follow-up PR to add it to `cli/azd/extensions/registry.json`. Users who installed from the dev registry will be **automatically promoted** to the main registry on their next `azd extension update`. > [!NOTE] > Extensions in the dev registry have no stability guarantees, are unsigned, and are not covered by Azure support. This is expected and appropriate for pre-release testing. See the [Dev/Experimental Extension Registry](../../cli/azd/docs/extensions/extension-resolution-and-versioning.md#devexperimental-extension-registry) guide for full details. From 748faaf5077e80af49a5c2b2e23ac33b9bbe862f Mon Sep 17 00:00:00 2001 From: hyoshis Date: Tue, 21 Jul 2026 14:31:54 -0700 Subject: [PATCH 02/10] UX Guideline: add responsive table layout and agentic UX style Adds two sets of UX guidance to the azd style guides: 1. Responsive list/table layouts for list commands, documented as part of the core azd design patterns in azd-style-guide.md. 2. Agentic (AI / GitHub Copilot) UX patterns, moved into a dedicated agentic-ux-style-guide.md so the core guide focuses solely on core azd design patterns (which extensions may follow for consistency, but are not enforced). Adds cross-references in the core guide's overview and in cli/azd/AGENTS.md so agents reference the correct file for the flow they're working on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 656aee2e-29aa-4b53-965c-63543c1dd44a --- cli/azd/AGENTS.md | 13 +- .../agentic-ux-style-guide.md | 104 +++++++++++++++ .../docs/style-guidelines/azd-style-guide.md | 126 +++++++++++++++++- 3 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 cli/azd/docs/style-guidelines/agentic-ux-style-guide.md diff --git a/cli/azd/AGENTS.md b/cli/azd/AGENTS.md index 8961a421b46..667eeed21df 100644 --- a/cli/azd/AGENTS.md +++ b/cli/azd/AGENTS.md @@ -174,9 +174,9 @@ func (a *myAction) Run(ctx context.Context) (*actions.ActionResult, error) { ### CLI UX & Style -When working on CLI output, terminal UX, spinners, progress states, colors, or prompts for **core azd flows**, you **MUST read** the style guide before making any changes or recommendations: +When working on CLI output, terminal UX, spinners, progress states, colors, or prompts, you **MUST read** the relevant style guide before making any changes or recommendations. Reference the right file for the flow you're working on: -📄 **`cli/azd/docs/style-guidelines/azd-style-guide.md`** (full path from repo root) +📄 **`cli/azd/docs/style-guidelines/azd-style-guide.md`** — **core azd design patterns** (full path from repo root) This file is the authoritative reference for core azd terminal UX patterns including: - Progress report states (`(✓) Done`, `(x) Failed`, `(!) Warning`, `(-) Skipped`) @@ -185,7 +185,11 @@ This file is the authoritative reference for core azd terminal UX patterns inclu - User input patterns (text input, list select, yes/no confirm) - Prompt styling (`?` marker in bold blue, `[Type ? for hint]`, post-submit states) -> **Note**: This style guide covers **core azd flows only**. Separate guidelines for agentic flows and extension-specific UX will be provided in dedicated files in the future. Do not apply core azd patterns to agentic or extension flows without a dedicated style reference. +📄 **`cli/azd/docs/style-guidelines/agentic-ux-style-guide.md`** — **agentic (AI / GitHub Copilot) UX patterns** + +Read this file instead when working on the AI-driven / agentic experience (the "Set up with GitHub Copilot (Preview)" flow, `azd agent`, the copilot session runtime, and the `AgentDisplay` renderer). It documents the distinct magenta + glyph visual language for agent identity, tool activity, subagents, and thinking states. + +> **Note**: These are two distinct visual systems. The core guide's patterns are **not enforced on extensions**, but extension developers are encouraged to follow them where applicable for consistency. Do **not** apply core azd status patterns to agentic flows (or vice versa) — pick the guide that matches the flow. Extension-specific UX is documented in `cli/azd/docs/extensions/extensions-style-guide.md`. ### Code Organization @@ -409,7 +413,8 @@ go build Feature-specific docs are in `docs/` — refer to them as needed. Some key docs include: -- `docs/style-guidelines/azd-style-guide.md` - CLI style guide (colors, spinners, progress states, terminal UX) +- `docs/style-guidelines/azd-style-guide.md` - Core azd CLI style guide (colors, spinners, progress states, terminal UX) +- `docs/style-guidelines/agentic-ux-style-guide.md` - Agentic (AI / GitHub Copilot) UX patterns (magenta + glyph vocabulary) - `docs/style-guidelines/new-azd-command.md` - Adding new commands - `docs/extensions/extension-framework.md` - Extension development using gRPC extension framework - `docs/style-guidelines/guiding-principles.md` - Design principles diff --git a/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md b/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md new file mode 100644 index 00000000000..7b82adf3ebf --- /dev/null +++ b/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md @@ -0,0 +1,104 @@ +# Azure Developer CLI (`azd`) Agentic UX Style Guide + +## Overview + +This guide covers the **agentic (AI / GitHub Copilot) UX patterns** for the Azure Developer CLI. These are a **deliberately distinct visual system** from the core azd flows documented in [azd-style-guide.md](./azd-style-guide.md). + +> **Scope**: This guide is for the AI-driven / agentic experience only. For core azd terminal UX (progress reports, spinners, colors, prompts, responsive list/table layouts) see [azd-style-guide.md](./azd-style-guide.md). For extension-specific UX see [extensions-style-guide.md](../extensions/extensions-style-guide.md). + +> **Do not blend the two systems.** Core azd status colors (green `(✓)`, red `(x)`, yellow `(!)`) still mean success/failure/warning inside agentic output, but the agent's own identity, tool activity, and thinking states use the magenta + glyph vocabulary described here. Never use magenta for core-flow status, and never restyle core progress reports with the agentic glyphs. Note that agentic **prompts deliberately reuse the core prompt conventions** (the blue `?` marker); it is only the agent's own status/output that uses the agentic vocabulary. + +## Agentic (AI / GitHub Copilot) UX Patterns + +azd has an **agentic experience** — the AI-driven flow entered when a user picks **"Set up with GitHub Copilot (Preview)"** during `azd init`, and used by the `azd agent` / copilot session runtime. This experience has a **deliberately distinct visual language** from core azd flows. Its signature is **magenta**, which marks AI/agent identity and active AI work. + +> **Do not blend the two systems.** Core azd status colors (green `(✓)`, red `(x)`, yellow `(!)`) still mean success/failure/warning inside agentic output, but the agent's own identity, tool activity, and thinking states use the magenta + glyph vocabulary described here. Never use magenta for core-flow status, and never restyle core progress reports with the agentic glyphs. + +- Entry point (init branch): [`cmd/init.go`](../../cmd/init.go) — `promptInitType()` adds the Copilot option and `initAppWithAgent()` runs the flow. +- Agent display/event renderer: [`internal/agent/display.go`](../../internal/agent/display.go) (`AgentDisplay`). +- Branding constant: `DisplayTitle = "GitHub Copilot"` in `internal/agent/copilot/config_keys.go`. + +### Branding & Naming + +- Use **"GitHub Copilot"** for the user-facing name (via the `DisplayTitle` constant — don't hardcode the string). The `azd init` menu item is **"Set up with GitHub Copilot (Preview)"**. +- The experience is in **preview**: the entry flow prints a preview notice (with a link to `https://aka.ms/azd-feature-stages`) and tells users how to change permissions later via `azd copilot consent`. Keep this notice when touching the flow. + +### Magenta: The Agentic Signature Color + +There is **no `WithMagentaFormat` helper** in `pkg/output/colors.go`. The only magenta-mapped helper is `WithHintFormat` (`color.MagentaString`), which is a **shared** hint color used in both core and agentic flows (spinner glyph, prompt hints, next-step messages) — its magenta is *not* exclusive to agentic UX. The agentic **agent-identity** styling is different: it applies magenta **directly** through `color.MagentaString` from `fatih/color` to agent/tool/subagent names and in-progress AI messages. When flagging violations, distinguish shared `WithHintFormat` hints (allowed anywhere) from direct `color.MagentaString` agent-identity styling (agentic only). + +Magenta agent-identity styling semantically means **"this is the AI agent / AI is actively working"**. It is applied to: + +| Element | Rendering | Location | +| ---------------------------- | ----------------------------------------------------- | -------- | +| Spinner glyph | Animated glyph in magenta via `WithHintFormat` | `pkg/ux/spinner.go` | +| Running tool name | `Running ` — tool name magenta | `internal/agent/display.go` (`SessionEventTypeToolExecutionStarted`) | +| Tool completion (verb) | `✔︎ Ran ` — `` magenta for `powershell`/generic tools | `toolVerb()` in `display.go` | +| Failed tool name | `✖ ` — name magenta (glyph red) | `printToolState()` in `display.go` | +| Subagent banner | `◆ ` in magenta | `SessionEventTypeSubagentStarted` | +| Subagent completed name | `✔︎ completed` — name magenta (check green) | `SessionEventTypeSubagentCompleted` | +| Init "preparing" message | `Preparing application for Azure deployment...` magenta | `cmd/init.go` | + +Reserve **direct `color.MagentaString`** styling for these agent-identity / active-AI-work cases. Do not magenta-color arbitrary text. (Shared `WithHintFormat` hints are a separate, allowed use of magenta.) + +### Agentic Glyph & Layout Vocabulary + +The agent renderer uses a distinct glyph set (not the core `(✓)/(x)/(!)` prefixes): + +| Glyph | Meaning | Color | +| ----- | ------------------------------ | --------------------------------------- | +| `◆` | Subagent started | Magenta | +| `◇` | Skill invoked (`◇ Using skill: `) | Cyan (`color.CyanString`); optional gray `from @` | +| `✔︎` | Tool / subagent succeeded | Green check; label follows (verb or name) | +| `✖` | **Tool** failed | Red glyph + magenta tool name; optional error detail in red on a `└` line | +| `✖` | **Subagent** failed | Entire single line rendered red (`✖ failed: `) | +| `├` / `└` | Sub-detail / nested tree lines (shell command, MCP args, error) | Gray (`color.HiBlackString`) | + +- **Nested (subagent) tool calls are indented** two spaces to show hierarchy. +- **Assistant messages** are rendered as terminal markdown via `output.WithMarkdown(...)`, not plain text. +- **Reasoning / "thinking"**: `AgentDisplay` initializes the spinner with the text `Thinking...` (overriding the shared spinner's default `Loading...`); streamed reasoning shows above the spinner in **dim gray** (`color.HiBlackString`), truncated to the last five lines. +- **Cancel affordance**: the live canvas shows `Press Ctrl+C to cancel` in gray with a bold `Ctrl+C`. + +### The Agent Display Canvas + +Live agent output is drawn on a `uxlib.Canvas` (`AgentDisplay.Start`) composed, top to bottom, of: recent reasoning (dim gray) → blank separator → spinner (magenta glyph + status text) → sub-detail tree (`├`/`└`, gray) → `Press Ctrl+C to cancel`. Completed events (`◆`/`◇`/`✔︎`/`✖`) are printed above the canvas and persist; the transient spinner/reasoning region is cleared and redrawn. + +### Prompts in Agentic Flows + +Agentic flows **reuse the core prompt component** ([`pkg/ux/prompt.go`](../../pkg/ux/prompt.go)), so user prompts still use the **blue `?`** marker described in [User Inputs](./azd-style-guide.md#user-inputs). **Agent prompts do not use magenta markers.** Magenta is reserved for agent identity and in-progress AI work, not for soliciting input. When adding prompts inside an agentic flow, follow the core User Inputs rules — do not restyle the `?` marker. + +### Agentic Output Examples + +**Thinking / reasoning + spinner** (spinner glyph is the default `|` `/` `-` `\` animation, rendered magenta): + +``` + ...streamed reasoning text (dim gray)... + + / Thinking... + Press Ctrl+C to cancel +``` + +**Tool run and completion:** + +``` +Running powershell go build... +✔︎ Ran powershell +``` + +**Subagent lifecycle:** + +``` +◆ GitHub Copilot + └ Generates Azure app scaffolding +✔︎ GitHub Copilot completed +``` + +> Colors: `◆` and the subagent/tool names → magenta (`color.MagentaString`). `✔︎` → green. `✖` and error detail → red. Reasoning, sub-detail (`├`/`└`), and hints → gray. `◇ Using skill:` → cyan. Assistant prose → `WithMarkdown`. + +### Agentic UX Guidelines + +- **Keep the two systems separate.** Don't apply core progress-report prefixes to agent events, and don't apply agentic glyphs/magenta to core commands. +- **Direct magenta = agent identity / active AI work.** Use `color.MagentaString` for agent/tool/subagent names and in-progress AI messages — not for arbitrary text. (The shared `WithHintFormat` hint color is separate and allowed in any flow.) +- **Reuse the shared prompt and spinner components** — don't fork a magenta prompt marker. +- **Preserve the preview notice and consent guidance** in the init entry flow. +- **Render assistant messages as markdown** (`output.WithMarkdown`) so formatting is legible in the terminal. diff --git a/cli/azd/docs/style-guidelines/azd-style-guide.md b/cli/azd/docs/style-guidelines/azd-style-guide.md index 1b79ff10882..3e289898088 100644 --- a/cli/azd/docs/style-guidelines/azd-style-guide.md +++ b/cli/azd/docs/style-guidelines/azd-style-guide.md @@ -4,7 +4,13 @@ This style guide establishes standards for code, user experience, testing, and documentation across the Azure Developer CLI project. Following these guidelines ensures consistency, maintainability, and a high-quality user experience. -> **Scope**: This guide covers **core azd flows** only. Separate guidelines for agentic flows and extension-specific UX will be provided in dedicated files in the future. +> **This guide covers core azd design patterns**, including core azd flows and the responsive list/table layouts shared across list commands (`azd tool list`, `azd tool check`, `azd extension list`, etc.). These core patterns are **not enforced on extensions**, but extension developers are encouraged to follow them where applicable for consistent, familiar UX. +> +> **Related guides — reference the right file:** +> - **Agentic (AI / GitHub Copilot) UX** → [agentic-ux-style-guide.md](./agentic-ux-style-guide.md). The AI-driven / agentic experience uses a **deliberately distinct** magenta + glyph visual language. Do not apply core azd patterns to agentic flows, or vice versa. (Note: agentic **prompts deliberately reuse the core prompt conventions** — the blue `?` marker; only the agent's own status/output uses the agentic vocabulary.) +> - **Extension-specific UX** → [extensions-style-guide.md](../extensions/extensions-style-guide.md). +> +> Core azd and agentic styling are distinct visual systems — do not mix their conventions (for example, don't use the agentic magenta *agent-identity* styling in core flows, and don't restyle core progress reports with the agentic glyphs). ## Code Style Guidelines @@ -297,6 +303,124 @@ Once the user makes their selection: > Colors: `?` → `WithHighLightFormat` + `WithBold`. `(US) East US 2 (eastus2)` (selected value) → `WithHighLightFormat`. +### Responsive List & Table Layouts + +Commands that print a list of items — `azd tool list`, `azd tool check`, `azd extension list` (and its alias `azd ext list`), `azd extension source list`, `azd template list`, `azd template source list`, `azd copilot consent list` — render through the shared responsive formatter rather than hand-rolling their own `tabwriter` output. This keeps column names, colors, and layout behavior consistent, and gives every list command a layout that adapts to the terminal width. + +**Any new or migrated list command MUST use this formatter.** A few legacy commands (e.g. `azd env list`, which still uses the plain `TableFormatter`) predate it — migrate them when touched rather than adding another bespoke table. + +The shared component is `output.PrettyTableFormatter`, configured via `output.PrettyTableFormatterOptions`. + +- Formatter & breakpoint logic: [`pkg/output/pretty_table.go`](../../pkg/output/pretty_table.go) +- Column & option types: [`pkg/output/pretty_table_types.go`](../../pkg/output/pretty_table_types.go) +- Table rendering: [`pkg/output/pretty_table_table.go`](../../pkg/output/pretty_table_table.go) +- Card rendering: [`pkg/output/pretty_table_cards.go`](../../pkg/output/pretty_table_cards.go) +- Width/layout math: [`pkg/output/pretty_table_layout.go`](../../pkg/output/pretty_table_layout.go) + +> Reference implementations: `azd tool list` in [`cmd/tool.go`](../../cmd/tool.go), `azd extension list` in [`cmd/extension.go`](../../cmd/extension.go). Model new list commands on these. + +#### Responsive Breakpoints + +The formatter selects one of three layouts from the current terminal width (`resolveBreakpoint`). Defaults live in `pretty_table_types.go` and can be overridden per command via `FullThreshold` / `CompactThreshold`. + +| Layout | Width condition | What renders | +| ----------- | -------------------------------------- | ------------------------------------------------------------------------ | +| **Full** | `width >= 100` (`DefaultFullThreshold`) | All columns; long values may still wrap/truncate to fit | +| **Compact** | `width >= 60` (`DefaultCompactThreshold`) | Only columns with `Priority <= 2`; the dropped columns collapse into a single header-only `···` marker | +| **Card** | `width < 60` | One card per row, optionally grouped into sections | + +- Do **not** invent new width thresholds per command. Use the shared defaults unless there is a strong, documented reason to override them. +- Set `ForceCards: true` only when a command should always render cards regardless of width. + +#### Defining Columns + +Each column is a `PrettyColumn` (a `Column` plus responsive metadata). Conventions: + +- **Headings are UPPERCASE** and short (`ID`, `NAME`, `STATUS`, `INSTALLED`, `LATEST`, `SOURCE`, `CATEGORY`, `TYPE`, `LOCATION`). Reuse these standard names across commands — don't introduce a synonym like `VERSION` when `INSTALLED`/`LATEST` already express the concept. +- **`Priority`** controls compact visibility: `1` and `2` survive the compact breakpoint; `3+` are full-table only; `0` is treated as `1`. Keep the identifying columns (`ID`, `STATUS`, `INSTALLED`) at priority `1`. +- **`ColorFunc`** applies semantic color to a cell (see status colors below). Use the standard `output.With*Format` helpers, never raw ANSI. +- **`Truncatable`** shortens a value with an ellipsis (`…`), keeping at least `minVisibleChars` (5) characters. +- **`Wrappable`** lets a value wrap onto at most `maxWrapLines` (2) lines before truncating — preferred for identifier columns like `NAME`. +- **`CardTitle`** promotes the column's value to the highlighted (blue, `WithHighLightFormat`) title line of each card (no label), and excludes it from the labeled card fields. Typically set on `NAME`. +- **`CardValueTemplate`** provides an alternate template for the card layout, e.g. to omit a `LATEST` value in cards when it equals `INSTALLED`. (Cards omit empty fields entirely; they do not print a `-` placeholder.) +- **`CardGroupColumn`** (an option, not a column) groups cards under section headers by that column's value (e.g. group by `SOURCE` or `CATEGORY`). Grouping preserves the input order of rows — it does **not** sort; sort your rows before formatting. +- **`ResponsiveColumnHint: true`** enables two related behaviors: the header-only `···` placeholder column (shown only at the compact breakpoint when columns are dropped) **and** the "Showing N of M columns…" hint line (shown whenever columns are hidden *or* a value is truncated, including in the full layout). Enable it for user-facing lists. + +```go +columns := []output.PrettyColumn{ + {Column: output.Column{Heading: "ID", ValueTemplate: "{{.Id}}"}, Priority: 1}, + { + Column: output.Column{Heading: "NAME", ValueTemplate: "{{.DisplayName}}"}, + Priority: 2, + CardTitle: true, + Wrappable: true, + Truncatable: true, + }, + { + Column: output.Column{Heading: "STATUS", ValueTemplate: "{{.Status}}"}, + Priority: 1, + ColorFunc: extensionStatusColor, // semantic status color + }, +} +formatter.Format(rows, writer, output.PrettyTableFormatterOptions{ + Columns: columns, + CardGroupColumn: "SOURCE", + ResponsiveColumnHint: true, +}) +``` + +#### Header, Spacing & Status Styling + +- **Header row**: bold high-white (`color.New(color.Bold, color.FgHiWhite)`, equivalent to `WithBold`), followed by a **gray** underline rule (`WithGrayFormat`). +- **Column spacing**: a fixed `columnPadding` (3 spaces). **No box-drawing separators** and **no `tabwriter`** in list tables — `tabwriter` is reserved for the `show`/detail screens. +- **Empty values**: in the **table** layouts, render a literal `-` for a missing value (e.g. `INSTALLED` with no installed version). This is done in the column's `ValueTemplate` (e.g. `{{if .Version}}{{.Version}}{{else}}-{{end}}`), not automatically by the formatter. In the **card** layout, empty fields are omitted rather than shown as `-`. +- **Status colors** are centralized in per-command helpers (`toolStatusColor`, `extensionStatusColor`) that map a status string to a color helper. Keep these mappings consistent: + +| Status text | Meaning | Color helper | +| ------------------- | ----------------------------- | ------------------- | +| `Installed` / `Up to date` | Present and current | `WithSuccessFormat` | +| `Upgrade available` | Installed but outdated | `WithWarningFormat` | +| `Incompatible` | Cannot be used | `WithErrorFormat` | +| `Not installed` | Absent (not an error) | `WithGrayFormat` | + +#### Layout Examples + +**Full table** (`width >= 100`) — every column, gray underline rule: + +``` +ID NAME STATUS INSTALLED LATEST SOURCE +──────────────────────────────────────────────────────────────────────────────────────────────────── +az-cli Azure CLI Up to date 1.0.0 1.0.0 azd +``` + +**Compact table** (`60 <= width < 100`) — only `Priority <= 2` columns; the dropped columns collapse into a single header-only `···` column (data rows leave it blank), with a hint line: + +``` +ID NAME STATUS INSTALLED ··· +──────────────────────────────────────────────────────────────────────────────────────── +az-cli Azure CLI Up to date 1.0.0 + +Showing 4 of 5 columns. Resize the terminal or run with -o json for full details. +``` + +**Card layout** (`width < 60`) — one card per row, grouped by `CardGroupColumn`, `CardTitle` value as the heading: + +``` +── SOURCE: azd ───────────────────── + +Azure CLI +ID: az-cli +STATUS: Up to date +INSTALLED: 1.0.0 +``` + +#### List Command Guidelines + +- **Always support `-o json`.** The responsive layouts are for humans; scripts and full detail should use `--output json`. The compact hint explicitly points users there. +- **Empty state**: print a clear, single-line message instead of an empty table (e.g. `No tools found in the registry.`, `No tools found.`). Extension list adds guidance on how to add a source. +- **Stable ordering**: the formatter does not sort — it renders (and groups cards) in the order rows are passed in. **Sort your rows before formatting** and group by a stable key (`tool list` by `CATEGORY`, `extension list` by `SOURCE`) so output is deterministic. +- **Don't hand-roll a new table.** If you find yourself writing `tabwriter` for a list of items, use `PrettyTableFormatter` instead so the command inherits responsive behavior for free. + ### CLI Color Standards The CLI uses consistent color formatting through helper functions defined in [`cli/azd/pkg/output/colors.go`](../../pkg/output/colors.go). **Always use these helper functions** instead of writing raw ANSI escape codes. From 021a845871230a4a10e7728dde163dda878400bc Mon Sep 17 00:00:00 2001 From: hyoshis Date: Thu, 30 Jul 2026 11:31:47 -0700 Subject: [PATCH 03/10] docs: clarify agentic experience entry point Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: be625a8a-0787-45ea-b3d9-a782d4c2651f --- cli/azd/docs/style-guidelines/agentic-ux-style-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md b/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md index 7b82adf3ebf..d0b1ac03099 100644 --- a/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md +++ b/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md @@ -10,7 +10,7 @@ This guide covers the **agentic (AI / GitHub Copilot) UX patterns** for the Azur ## Agentic (AI / GitHub Copilot) UX Patterns -azd has an **agentic experience** — the AI-driven flow entered when a user picks **"Set up with GitHub Copilot (Preview)"** during `azd init`, and used by the `azd agent` / copilot session runtime. This experience has a **deliberately distinct visual language** from core azd flows. Its signature is **magenta**, which marks AI/agent identity and active AI work. +azd has an **agentic experience** — the AI-driven flow entered when a user picks **"Set up with GitHub Copilot (Preview)"** during `azd init`. This experience has a **deliberately distinct visual language** from core azd flows. Its signature is **magenta**, which marks AI/agent identity and active AI work. > **Do not blend the two systems.** Core azd status colors (green `(✓)`, red `(x)`, yellow `(!)`) still mean success/failure/warning inside agentic output, but the agent's own identity, tool activity, and thinking states use the magenta + glyph vocabulary described here. Never use magenta for core-flow status, and never restyle core progress reports with the agentic glyphs. From 730080c37921cc5b778c24f364d43a8fc5ec6831 Mon Sep 17 00:00:00 2001 From: hyoshis Date: Fri, 31 Jul 2026 20:45:44 -0700 Subject: [PATCH 04/10] Fix update command rename follow-ups Address review feedback, complete customer-facing terminology updates, preserve compatibility, and remove unrelated documentation changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/AGENTS.md | 13 +- cli/azd/cmd/auto_install_test.go | 2 +- cli/azd/cmd/extension.go | 4 +- cli/azd/cmd/extension_bundle_test.go | 4 +- cli/azd/cmd/middleware/extension_activator.go | 2 +- cli/azd/cmd/middleware/extensions.go | 8 +- cli/azd/cmd/project_extension_auto_install.go | 2 +- cli/azd/cmd/telemetry_test.go | 4 +- cli/azd/cmd/testdata/TestFigSpec.ts | 1 + cli/azd/cmd/tool.go | 14 +- cli/azd/cmd/tool_test.go | 16 ++- .../docs/extensions/extension-framework.md | 8 +- .../extension-resolution-and-versioning.md | 8 +- .../agentic-ux-style-guide.md | 104 --------------- .../docs/style-guidelines/azd-style-guide.md | 126 +----------------- cli/azd/docs/tracing-in-azd.md | 4 +- .../internal/cmd/doctor/checks_local.go | 2 +- .../internal/cmd/doctor/checks_local_test.go | 2 +- .../azure.ai.agents/internal/cmd/init.go | 2 +- .../extensions/azure.coding-agent/README.md | 2 +- cli/azd/internal/figspec/customizations.go | 2 +- .../internal/figspec/fig_generators_test.go | 2 +- cli/azd/internal/tracing/fields/fields.go | 4 +- cli/azd/pkg/extensions/registry_version.go | 6 +- .../pkg/extensions/registry_version_test.go | 2 +- cli/azd/pkg/extensions/update_checker.go | 8 +- cli/azd/pkg/extensions/update_checker_test.go | 4 +- .../pkg/extensions/update_integration_test.go | 4 +- cli/azd/pkg/output/ux/warning_test.go | 16 +-- cli/azd/pkg/tool/detector.go | 2 +- cli/azd/pkg/tool/installer.go | 12 +- cli/azd/pkg/tool/installer_test.go | 20 +-- cli/azd/pkg/tools/bicep/bicep.go | 2 +- cli/azd/pkg/tools/bicep/bicep_test.go | 4 +- cli/azd/pkg/tools/pack/pack.go | 4 +- cli/azd/resources/error_suggestions.yaml | 4 +- docs/reference/telemetry-data.md | 2 +- .../metrics-audit/feature-telemetry-matrix.md | 12 +- docs/specs/metrics-audit/telemetry-schema.md | 2 +- 39 files changed, 110 insertions(+), 330 deletions(-) delete mode 100644 cli/azd/docs/style-guidelines/agentic-ux-style-guide.md diff --git a/cli/azd/AGENTS.md b/cli/azd/AGENTS.md index 667eeed21df..8961a421b46 100644 --- a/cli/azd/AGENTS.md +++ b/cli/azd/AGENTS.md @@ -174,9 +174,9 @@ func (a *myAction) Run(ctx context.Context) (*actions.ActionResult, error) { ### CLI UX & Style -When working on CLI output, terminal UX, spinners, progress states, colors, or prompts, you **MUST read** the relevant style guide before making any changes or recommendations. Reference the right file for the flow you're working on: +When working on CLI output, terminal UX, spinners, progress states, colors, or prompts for **core azd flows**, you **MUST read** the style guide before making any changes or recommendations: -📄 **`cli/azd/docs/style-guidelines/azd-style-guide.md`** — **core azd design patterns** (full path from repo root) +📄 **`cli/azd/docs/style-guidelines/azd-style-guide.md`** (full path from repo root) This file is the authoritative reference for core azd terminal UX patterns including: - Progress report states (`(✓) Done`, `(x) Failed`, `(!) Warning`, `(-) Skipped`) @@ -185,11 +185,7 @@ This file is the authoritative reference for core azd terminal UX patterns inclu - User input patterns (text input, list select, yes/no confirm) - Prompt styling (`?` marker in bold blue, `[Type ? for hint]`, post-submit states) -📄 **`cli/azd/docs/style-guidelines/agentic-ux-style-guide.md`** — **agentic (AI / GitHub Copilot) UX patterns** - -Read this file instead when working on the AI-driven / agentic experience (the "Set up with GitHub Copilot (Preview)" flow, `azd agent`, the copilot session runtime, and the `AgentDisplay` renderer). It documents the distinct magenta + glyph visual language for agent identity, tool activity, subagents, and thinking states. - -> **Note**: These are two distinct visual systems. The core guide's patterns are **not enforced on extensions**, but extension developers are encouraged to follow them where applicable for consistency. Do **not** apply core azd status patterns to agentic flows (or vice versa) — pick the guide that matches the flow. Extension-specific UX is documented in `cli/azd/docs/extensions/extensions-style-guide.md`. +> **Note**: This style guide covers **core azd flows only**. Separate guidelines for agentic flows and extension-specific UX will be provided in dedicated files in the future. Do not apply core azd patterns to agentic or extension flows without a dedicated style reference. ### Code Organization @@ -413,8 +409,7 @@ go build Feature-specific docs are in `docs/` — refer to them as needed. Some key docs include: -- `docs/style-guidelines/azd-style-guide.md` - Core azd CLI style guide (colors, spinners, progress states, terminal UX) -- `docs/style-guidelines/agentic-ux-style-guide.md` - Agentic (AI / GitHub Copilot) UX patterns (magenta + glyph vocabulary) +- `docs/style-guidelines/azd-style-guide.md` - CLI style guide (colors, spinners, progress states, terminal UX) - `docs/style-guidelines/new-azd-command.md` - Adding new commands - `docs/extensions/extension-framework.md` - Extension development using gRPC extension framework - `docs/style-guidelines/guiding-principles.md` - Design principles diff --git a/cli/azd/cmd/auto_install_test.go b/cli/azd/cmd/auto_install_test.go index 6358bb3f04f..19ce1fd34a7 100644 --- a/cli/azd/cmd/auto_install_test.go +++ b/cli/azd/cmd/auto_install_test.go @@ -2124,7 +2124,7 @@ func TestProjectExtensionErrorsCarrySuggestions(t *testing.T) { suggestErr, ok := errors.AsType[*internal.ErrorWithSuggestion](err) require.True(t, ok, "expected an ErrorWithSuggestion") assert.Contains(t, suggestErr.Error(), "does not satisfy constraint") - assert.Contains(t, suggestErr.Suggestion, "azd extension upgrade microsoft.foundry") + assert.Contains(t, suggestErr.Suggestion, "azd extension update microsoft.foundry") assert.NotContains(t, suggestErr.Suggestion, "--version >=1.0.0") }) diff --git a/cli/azd/cmd/extension.go b/cli/azd/cmd/extension.go index 96602c4e1dd..3bc3e9d908b 100644 --- a/cli/azd/cmd/extension.go +++ b/cli/azd/cmd/extension.go @@ -1058,7 +1058,7 @@ func (a *extensionInstallAction) Run(ctx context.Context) (*actions.ActionResult ) if err != nil { a.console.StopSpinner(ctx, stepMessage, input.StepFailed) - return nil, wrapDependencyError(fmt.Errorf("failed to update extension: %w", err)) + return nil, wrapDependencyError(fmt.Errorf("failed to update extension: %w", err)) } stepMessage += output.WithGrayFormat(" (%s)", extensionVersion.Version) @@ -1863,7 +1863,7 @@ func newExtensionUpgradeFlags(cmd *cobra.Command, global *internal.GlobalCommand return flags } -// azd extension upgrade +// azd extension update type extensionUpgradeAction struct { args []string flags *extensionUpgradeFlags diff --git a/cli/azd/cmd/extension_bundle_test.go b/cli/azd/cmd/extension_bundle_test.go index d280b21ab41..2c21dcef608 100644 --- a/cli/azd/cmd/extension_bundle_test.go +++ b/cli/azd/cmd/extension_bundle_test.go @@ -295,9 +295,9 @@ func TestVersionTransitionVerb(t *testing.T) { expected string }{ {"1.0.0", "1.0.0", "Reinstall"}, - {"1.0.0", "2.0.0", "Upgrade to 2.0.0"}, + {"1.0.0", "2.0.0", "Update to 2.0.0"}, {"1.0.0", "0.9.0", "Downgrade to 0.9.0"}, - {"1.0.0-preview", "1.0.0", "Upgrade to 1.0.0"}, + {"1.0.0-preview", "1.0.0", "Update to 1.0.0"}, // Non-semver tags have no defined ordering -> neutral verb. {"nightly", "1.0.0", "Replace with 1.0.0"}, {"1.0.0", "nightly", "Replace with nightly"}, diff --git a/cli/azd/cmd/middleware/extension_activator.go b/cli/azd/cmd/middleware/extension_activator.go index 3f362d502e8..dce07e97434 100644 --- a/cli/azd/cmd/middleware/extension_activator.go +++ b/cli/azd/cmd/middleware/extension_activator.go @@ -136,7 +136,7 @@ func (a *ExtensionActivator) EnsureProvisioningProviders( Suggestion: fmt.Sprintf( "Run with %s for details, or check for an update with %s", output.WithHighLightFormat("--debug"), - output.WithHighLightFormat("azd extension upgrade %s", ext.Id), + output.WithHighLightFormat("azd extension update %s", ext.Id), ), } } diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 94b0a8ae78f..8faf7d333b6 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -216,7 +216,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A info.ext.Id, info.result.InstalledVersion, info.result.LatestVersion, )) m.console.Message(ctx, fmt.Sprintf( - "To upgrade extension, run %s", output.WithHighLightFormat("azd extension upgrade %s", info.ext.Id), + "To update the extension, run %s", output.WithHighLightFormat("azd extension update %s", info.ext.Id), )) m.console.Message(ctx, "") } else if len(needsUpdate) > 1 { @@ -230,9 +230,9 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A )) } m.console.Message(ctx, fmt.Sprintf( - "Run %s to upgrade a specific extension, or %s to upgrade all extensions.", - output.WithHighLightFormat("azd extension upgrade "), - output.WithHighLightFormat("azd extension upgrade --all"), + "Run %s to update a specific extension, or %s to update all extensions.", + output.WithHighLightFormat("azd extension update "), + output.WithHighLightFormat("azd extension update --all"), )) m.console.Message(ctx, "") } diff --git a/cli/azd/cmd/project_extension_auto_install.go b/cli/azd/cmd/project_extension_auto_install.go index bb30dca74d3..332bde450bb 100644 --- a/cli/azd/cmd/project_extension_auto_install.go +++ b/cli/azd/cmd/project_extension_auto_install.go @@ -225,7 +225,7 @@ func validateInstalledExtensionVersion( versionPreference, ), Suggestion: fmt.Sprintf( - "Run 'azd extension upgrade %s' to move to the latest version, or "+ + "Run 'azd extension update %s' to move to the latest version, or "+ "'azd extension install %s --version ' to select an exact version "+ "that satisfies %q.", installed.Id, diff --git a/cli/azd/cmd/telemetry_test.go b/cli/azd/cmd/telemetry_test.go index b8bf78b3251..6be43023bcb 100644 --- a/cli/azd/cmd/telemetry_test.go +++ b/cli/azd/cmd/telemetry_test.go @@ -238,7 +238,7 @@ func TestCommandTelemetryCoverage(t *testing.T) { "extension install", // extension.source.kind "extension list", // extension.source.kind "extension show", // extension.source.kind - "extension upgrade", // extension.source.kind + extension upgrade spans + "extension update", // extension.source.kind + extension upgrade spans "hooks run", // hooks.name, hooks.type "infra generate", // infra.provider "init", // init.method, appinit.* fields @@ -250,7 +250,7 @@ func TestCommandTelemetryCoverage(t *testing.T) { "tool install", // tool.id(s), tool.dry_run, tool.install.* aggregate + per-tool fields "tool show", // tool.id "tool uninstall", // tool.id(s), tool.dry_run, tool.install.* aggregate + per-tool fields - "tool upgrade", // tool.id(s), tool.dry_run, tool.install.* aggregate + tool.upgrade.* versions + "tool update", // tool.id(s), tool.dry_run, tool.install.* aggregate + tool.upgrade.* versions "up", // infra.provider (via provisioning manager; composes provision+deploy) "update", // update.* fields } diff --git a/cli/azd/cmd/testdata/TestFigSpec.ts b/cli/azd/cmd/testdata/TestFigSpec.ts index dfc0d91c948..e689b47c842 100644 --- a/cli/azd/cmd/testdata/TestFigSpec.ts +++ b/cli/azd/cmd/testdata/TestFigSpec.ts @@ -6042,6 +6042,7 @@ const completionSpec: Fig.Spec = { args: { name: 'extension-id', isOptional: true, + generators: azdGenerators.listInstalledExtensions, }, }, ], diff --git a/cli/azd/cmd/tool.go b/cli/azd/cmd/tool.go index 671bd7c4df1..59279da4e99 100644 --- a/cli/azd/cmd/tool.go +++ b/cli/azd/cmd/tool.go @@ -28,7 +28,7 @@ import ( ) // singleResultCommonAttrs returns the usage attributes shared by single-target -// `azd tool install` and `azd tool upgrade`: success, tool.id, and the +// `azd tool install` and `azd tool update`: success, tool.id, and the // installation strategy. Callers append upgrade-specific version attrs // (tool.upgrade.{from,to}_version) on top. // @@ -1162,7 +1162,7 @@ func (a *toolInstallAction) resolveToolIds(ctx context.Context) ([]string, error } // --------------------------------------------------------------------------- -// azd tool upgrade [tool-name...] +// azd tool update [tool-name...] // --------------------------------------------------------------------------- type toolUpgradeFlags struct { @@ -1382,6 +1382,11 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err } if a.formatter.Kind() == output.JsonFormat { + // Keep the existing machine-readable action value stable for callers + // using either the update command or its upgrade alias. + for _, result := range upgradeResults { + result.Action = "upgrade" + } return nil, a.formatter.Format(upgradeResults, a.writer, nil) } @@ -1551,6 +1556,11 @@ func (a *toolUpgradeAction) dryRun( } if a.formatter.Kind() == output.JsonFormat { + // Keep the existing machine-readable action value stable for callers + // using either the update command or its upgrade alias. + for i := range rows { + rows[i].Action = "upgrade" + } return nil, a.formatter.Format(rows, a.writer, nil) } diff --git a/cli/azd/cmd/tool_test.go b/cli/azd/cmd/tool_test.go index 37de5c0d11d..3dc25498597 100644 --- a/cli/azd/cmd/tool_test.go +++ b/cli/azd/cmd/tool_test.go @@ -1351,7 +1351,7 @@ func TestToolUninstallAction_DryRun_DoesNotDelegate(t *testing.T) { } // TestToolUpgradeAction_All_UpgradesInstalledTools verifies that -// `azd tool upgrade --all` upgrades every installed tool (and only those), +// `azd tool update --all` updates every installed tool (and only those), // without an interactive selection prompt. func TestToolUpgradeAction_All_UpgradesInstalledTools(t *testing.T) { tracing.ResetUsageAttributesForTest() @@ -1403,7 +1403,7 @@ func TestToolUpgradeAction_All_UpgradesInstalledTools(t *testing.T) { } // TestToolUpgradeAction_All_JsonFormat_EmitsCleanJson exercises the reviewer's -// exact trigger — `azd tool upgrade --all --output json` — and verifies the +// exact trigger — `azd tool update --all --output json` — and verifies the // writer receives valid JSON. In JSON mode the detection spinner is // bypassed (detectAllTools) so no control bytes can corrupt the stream. func TestToolUpgradeAction_All_JsonFormat_EmitsCleanJson(t *testing.T) { @@ -1440,12 +1440,14 @@ func TestToolUpgradeAction_All_JsonFormat_EmitsCleanJson(t *testing.T) { var items []toolInstallResultItem require.NoError(t, json.Unmarshal(buf.Bytes(), &items), - "upgrade --all --output json must emit valid JSON") + "update --all --output json must emit valid JSON") require.NotEmpty(t, items, "at least one installed tool must be reported") + require.Equal(t, "upgrade", items[0].Action, + "the machine-readable action must remain stable for backward compatibility") } // TestToolUpgradeAction_All_JsonFormat_EmptyEmitsArray verifies that when there -// is nothing to upgrade, `azd tool upgrade --all --output json` still emits an +// is nothing to update, `azd tool update --all --output json` still emits an // empty result array ([]) rather than a consoleMessage object, so automation // sees one stable shape. func TestToolUpgradeAction_All_JsonFormat_EmptyEmitsArray(t *testing.T) { @@ -1484,7 +1486,7 @@ func TestToolUpgradeAction_All_JsonFormat_EmptyEmitsArray(t *testing.T) { assert.Empty(t, items) } -// TestToolUpgradeAction_IDsWithAll_Errors verifies that `azd tool upgrade foo +// TestToolUpgradeAction_IDsWithAll_Errors verifies that `azd tool update foo // --all` is rejected rather than silently ignoring foo and upgrading everything. func TestToolUpgradeAction_IDsWithAll_Errors(t *testing.T) { tracing.ResetUsageAttributesForTest() @@ -1514,7 +1516,7 @@ func TestToolUpgradeAction_IDsWithAll_Errors(t *testing.T) { } // TestToolUpgradeAction_NoPrompt_WithoutTarget_Errors verifies that -// `azd tool upgrade` with --no-prompt (or a non-interactive terminal) and no +// `azd tool update` with --no-prompt (or a non-interactive terminal) and no // tool IDs and no --all fails with guidance instead of implicitly upgrading // every installed tool — consistent with install/uninstall and azd's // --no-prompt contract. @@ -1555,7 +1557,7 @@ func TestToolUpgradeAction_NoPrompt_WithoutTarget_Errors(t *testing.T) { } // TestToolUpgradeAction_JsonOnTTY_WithoutTarget_Errors verifies that -// `azd tool upgrade --output json` on an interactive terminal (no --no-prompt) +// `azd tool update --output json` on an interactive terminal (no --no-prompt) // requires an explicit target rather than opening the no-argument picker, whose // output would corrupt the JSON result written to the same stdout. func TestToolUpgradeAction_JsonOnTTY_WithoutTarget_Errors(t *testing.T) { diff --git a/cli/azd/docs/extensions/extension-framework.md b/cli/azd/docs/extensions/extension-framework.md index 7f7c41f12a3..06867524084 100644 --- a/cli/azd/docs/extensions/extension-framework.md +++ b/cli/azd/docs/extensions/extension-framework.md @@ -3245,19 +3245,19 @@ Registry schema versions use `major.minor` format (e.g. `"1.0"`, `"1.1"`, `"2.0" |----------|----------| | Missing `schemaVersion` | Treated as `"1.0"` for backward compatibility | | Same major, newer minor (e.g. `"1.1"`) | Accepted silently — minor bumps are backward compatible | -| Newer major (e.g. `"2.0"`) | Rejected with an error and upgrade guidance | +| Newer major (e.g. `"2.0"`) | Rejected with an error and update guidance | | `0.x` (e.g. `"0.1"`) | Accepted — pre-release schema versions are valid | | Malformed version string | Rejected with a descriptive parse error | -### Upgrade Guidance +### Update Guidance When azd encounters a registry with a schema version it cannot support, it will -display an error with a suggestion to upgrade: +display an error with a suggestion to update: ``` ERROR: registry schema version 2.0 is not supported (max supported: 1.0) -Suggestion: Upgrade azd to the latest version to use this registry +Suggestion: Update azd to the latest version to use this registry https://aka.ms/azd/install ``` diff --git a/cli/azd/docs/extensions/extension-resolution-and-versioning.md b/cli/azd/docs/extensions/extension-resolution-and-versioning.md index 6d9c516e35f..999f68e0445 100644 --- a/cli/azd/docs/extensions/extension-resolution-and-versioning.md +++ b/cli/azd/docs/extensions/extension-resolution-and-versioning.md @@ -481,7 +481,7 @@ azd extension install my.experimental.extension --version 2.0.0-beta.1 --source If an extension exists in both the `azd` and `dev` sources and you do not specify `--source`, `azd` will prompt you to choose (in interactive mode) or return an error (in non-interactive mode). See [Handle Conflicts](#3-handle-conflicts) for details. -### Upgrade and Dev→Main Promotion +### Update and Dev→Main Promotion When you run `azd extension update`, extensions installed from the dev registry are evaluated for **one-way promotion** to the main registry. Promotion occurs automatically when: @@ -493,7 +493,7 @@ When promotion happens, the extension's stored source switches from `dev` to `az > [!NOTE] > If the main and dev registries have the **same** latest version, the extension stays on its current (dev) source. Equal versions are source-sticky. -The upgrade priority chain is: +The update priority chain is: 1. **Explicit `--source` flag** — always wins if provided 2. **Stored source** — the source the extension was originally installed from @@ -680,12 +680,12 @@ To remove the nightly registry later: azd extension source remove nightly ``` -### Upgrade and Nightly→Main Promotion +### Update and Nightly→Main Promotion Nightly versions use semver prerelease labels, so the standard `azd extension update` flow works: - A newer nightly (higher build id, or a higher base version) supersedes an older one, so `azd extension update` pulls the latest nightly. -- When the extension ships a **stable** release whose base version matches your nightly (for example stable `1.2.3` versus `1.2.3-nightly.200`), the stable release outranks the nightly and you are **automatically promoted** to the `azd` registry on your next upgrade. +- When the extension ships a **stable** release whose base version matches your nightly (for example stable `1.2.3` versus `1.2.3-nightly.200`), the stable release outranks the nightly and you are **automatically promoted** to the `azd` registry on your next update. > [!NOTE] > If your nightly was built from a **prerelease** base (for example `1.2.3-preview.nightly.60`), it sorts **above** the matching stable prerelease `1.2.3-preview`. In that case you are not promoted until the stable registry advances to a higher base version. This is expected semver precedence behavior. diff --git a/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md b/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md deleted file mode 100644 index d0b1ac03099..00000000000 --- a/cli/azd/docs/style-guidelines/agentic-ux-style-guide.md +++ /dev/null @@ -1,104 +0,0 @@ -# Azure Developer CLI (`azd`) Agentic UX Style Guide - -## Overview - -This guide covers the **agentic (AI / GitHub Copilot) UX patterns** for the Azure Developer CLI. These are a **deliberately distinct visual system** from the core azd flows documented in [azd-style-guide.md](./azd-style-guide.md). - -> **Scope**: This guide is for the AI-driven / agentic experience only. For core azd terminal UX (progress reports, spinners, colors, prompts, responsive list/table layouts) see [azd-style-guide.md](./azd-style-guide.md). For extension-specific UX see [extensions-style-guide.md](../extensions/extensions-style-guide.md). - -> **Do not blend the two systems.** Core azd status colors (green `(✓)`, red `(x)`, yellow `(!)`) still mean success/failure/warning inside agentic output, but the agent's own identity, tool activity, and thinking states use the magenta + glyph vocabulary described here. Never use magenta for core-flow status, and never restyle core progress reports with the agentic glyphs. Note that agentic **prompts deliberately reuse the core prompt conventions** (the blue `?` marker); it is only the agent's own status/output that uses the agentic vocabulary. - -## Agentic (AI / GitHub Copilot) UX Patterns - -azd has an **agentic experience** — the AI-driven flow entered when a user picks **"Set up with GitHub Copilot (Preview)"** during `azd init`. This experience has a **deliberately distinct visual language** from core azd flows. Its signature is **magenta**, which marks AI/agent identity and active AI work. - -> **Do not blend the two systems.** Core azd status colors (green `(✓)`, red `(x)`, yellow `(!)`) still mean success/failure/warning inside agentic output, but the agent's own identity, tool activity, and thinking states use the magenta + glyph vocabulary described here. Never use magenta for core-flow status, and never restyle core progress reports with the agentic glyphs. - -- Entry point (init branch): [`cmd/init.go`](../../cmd/init.go) — `promptInitType()` adds the Copilot option and `initAppWithAgent()` runs the flow. -- Agent display/event renderer: [`internal/agent/display.go`](../../internal/agent/display.go) (`AgentDisplay`). -- Branding constant: `DisplayTitle = "GitHub Copilot"` in `internal/agent/copilot/config_keys.go`. - -### Branding & Naming - -- Use **"GitHub Copilot"** for the user-facing name (via the `DisplayTitle` constant — don't hardcode the string). The `azd init` menu item is **"Set up with GitHub Copilot (Preview)"**. -- The experience is in **preview**: the entry flow prints a preview notice (with a link to `https://aka.ms/azd-feature-stages`) and tells users how to change permissions later via `azd copilot consent`. Keep this notice when touching the flow. - -### Magenta: The Agentic Signature Color - -There is **no `WithMagentaFormat` helper** in `pkg/output/colors.go`. The only magenta-mapped helper is `WithHintFormat` (`color.MagentaString`), which is a **shared** hint color used in both core and agentic flows (spinner glyph, prompt hints, next-step messages) — its magenta is *not* exclusive to agentic UX. The agentic **agent-identity** styling is different: it applies magenta **directly** through `color.MagentaString` from `fatih/color` to agent/tool/subagent names and in-progress AI messages. When flagging violations, distinguish shared `WithHintFormat` hints (allowed anywhere) from direct `color.MagentaString` agent-identity styling (agentic only). - -Magenta agent-identity styling semantically means **"this is the AI agent / AI is actively working"**. It is applied to: - -| Element | Rendering | Location | -| ---------------------------- | ----------------------------------------------------- | -------- | -| Spinner glyph | Animated glyph in magenta via `WithHintFormat` | `pkg/ux/spinner.go` | -| Running tool name | `Running ` — tool name magenta | `internal/agent/display.go` (`SessionEventTypeToolExecutionStarted`) | -| Tool completion (verb) | `✔︎ Ran ` — `` magenta for `powershell`/generic tools | `toolVerb()` in `display.go` | -| Failed tool name | `✖ ` — name magenta (glyph red) | `printToolState()` in `display.go` | -| Subagent banner | `◆ ` in magenta | `SessionEventTypeSubagentStarted` | -| Subagent completed name | `✔︎ completed` — name magenta (check green) | `SessionEventTypeSubagentCompleted` | -| Init "preparing" message | `Preparing application for Azure deployment...` magenta | `cmd/init.go` | - -Reserve **direct `color.MagentaString`** styling for these agent-identity / active-AI-work cases. Do not magenta-color arbitrary text. (Shared `WithHintFormat` hints are a separate, allowed use of magenta.) - -### Agentic Glyph & Layout Vocabulary - -The agent renderer uses a distinct glyph set (not the core `(✓)/(x)/(!)` prefixes): - -| Glyph | Meaning | Color | -| ----- | ------------------------------ | --------------------------------------- | -| `◆` | Subagent started | Magenta | -| `◇` | Skill invoked (`◇ Using skill: `) | Cyan (`color.CyanString`); optional gray `from @` | -| `✔︎` | Tool / subagent succeeded | Green check; label follows (verb or name) | -| `✖` | **Tool** failed | Red glyph + magenta tool name; optional error detail in red on a `└` line | -| `✖` | **Subagent** failed | Entire single line rendered red (`✖ failed: `) | -| `├` / `└` | Sub-detail / nested tree lines (shell command, MCP args, error) | Gray (`color.HiBlackString`) | - -- **Nested (subagent) tool calls are indented** two spaces to show hierarchy. -- **Assistant messages** are rendered as terminal markdown via `output.WithMarkdown(...)`, not plain text. -- **Reasoning / "thinking"**: `AgentDisplay` initializes the spinner with the text `Thinking...` (overriding the shared spinner's default `Loading...`); streamed reasoning shows above the spinner in **dim gray** (`color.HiBlackString`), truncated to the last five lines. -- **Cancel affordance**: the live canvas shows `Press Ctrl+C to cancel` in gray with a bold `Ctrl+C`. - -### The Agent Display Canvas - -Live agent output is drawn on a `uxlib.Canvas` (`AgentDisplay.Start`) composed, top to bottom, of: recent reasoning (dim gray) → blank separator → spinner (magenta glyph + status text) → sub-detail tree (`├`/`└`, gray) → `Press Ctrl+C to cancel`. Completed events (`◆`/`◇`/`✔︎`/`✖`) are printed above the canvas and persist; the transient spinner/reasoning region is cleared and redrawn. - -### Prompts in Agentic Flows - -Agentic flows **reuse the core prompt component** ([`pkg/ux/prompt.go`](../../pkg/ux/prompt.go)), so user prompts still use the **blue `?`** marker described in [User Inputs](./azd-style-guide.md#user-inputs). **Agent prompts do not use magenta markers.** Magenta is reserved for agent identity and in-progress AI work, not for soliciting input. When adding prompts inside an agentic flow, follow the core User Inputs rules — do not restyle the `?` marker. - -### Agentic Output Examples - -**Thinking / reasoning + spinner** (spinner glyph is the default `|` `/` `-` `\` animation, rendered magenta): - -``` - ...streamed reasoning text (dim gray)... - - / Thinking... - Press Ctrl+C to cancel -``` - -**Tool run and completion:** - -``` -Running powershell go build... -✔︎ Ran powershell -``` - -**Subagent lifecycle:** - -``` -◆ GitHub Copilot - └ Generates Azure app scaffolding -✔︎ GitHub Copilot completed -``` - -> Colors: `◆` and the subagent/tool names → magenta (`color.MagentaString`). `✔︎` → green. `✖` and error detail → red. Reasoning, sub-detail (`├`/`└`), and hints → gray. `◇ Using skill:` → cyan. Assistant prose → `WithMarkdown`. - -### Agentic UX Guidelines - -- **Keep the two systems separate.** Don't apply core progress-report prefixes to agent events, and don't apply agentic glyphs/magenta to core commands. -- **Direct magenta = agent identity / active AI work.** Use `color.MagentaString` for agent/tool/subagent names and in-progress AI messages — not for arbitrary text. (The shared `WithHintFormat` hint color is separate and allowed in any flow.) -- **Reuse the shared prompt and spinner components** — don't fork a magenta prompt marker. -- **Preserve the preview notice and consent guidance** in the init entry flow. -- **Render assistant messages as markdown** (`output.WithMarkdown`) so formatting is legible in the terminal. diff --git a/cli/azd/docs/style-guidelines/azd-style-guide.md b/cli/azd/docs/style-guidelines/azd-style-guide.md index 3e289898088..1b79ff10882 100644 --- a/cli/azd/docs/style-guidelines/azd-style-guide.md +++ b/cli/azd/docs/style-guidelines/azd-style-guide.md @@ -4,13 +4,7 @@ This style guide establishes standards for code, user experience, testing, and documentation across the Azure Developer CLI project. Following these guidelines ensures consistency, maintainability, and a high-quality user experience. -> **This guide covers core azd design patterns**, including core azd flows and the responsive list/table layouts shared across list commands (`azd tool list`, `azd tool check`, `azd extension list`, etc.). These core patterns are **not enforced on extensions**, but extension developers are encouraged to follow them where applicable for consistent, familiar UX. -> -> **Related guides — reference the right file:** -> - **Agentic (AI / GitHub Copilot) UX** → [agentic-ux-style-guide.md](./agentic-ux-style-guide.md). The AI-driven / agentic experience uses a **deliberately distinct** magenta + glyph visual language. Do not apply core azd patterns to agentic flows, or vice versa. (Note: agentic **prompts deliberately reuse the core prompt conventions** — the blue `?` marker; only the agent's own status/output uses the agentic vocabulary.) -> - **Extension-specific UX** → [extensions-style-guide.md](../extensions/extensions-style-guide.md). -> -> Core azd and agentic styling are distinct visual systems — do not mix their conventions (for example, don't use the agentic magenta *agent-identity* styling in core flows, and don't restyle core progress reports with the agentic glyphs). +> **Scope**: This guide covers **core azd flows** only. Separate guidelines for agentic flows and extension-specific UX will be provided in dedicated files in the future. ## Code Style Guidelines @@ -303,124 +297,6 @@ Once the user makes their selection: > Colors: `?` → `WithHighLightFormat` + `WithBold`. `(US) East US 2 (eastus2)` (selected value) → `WithHighLightFormat`. -### Responsive List & Table Layouts - -Commands that print a list of items — `azd tool list`, `azd tool check`, `azd extension list` (and its alias `azd ext list`), `azd extension source list`, `azd template list`, `azd template source list`, `azd copilot consent list` — render through the shared responsive formatter rather than hand-rolling their own `tabwriter` output. This keeps column names, colors, and layout behavior consistent, and gives every list command a layout that adapts to the terminal width. - -**Any new or migrated list command MUST use this formatter.** A few legacy commands (e.g. `azd env list`, which still uses the plain `TableFormatter`) predate it — migrate them when touched rather than adding another bespoke table. - -The shared component is `output.PrettyTableFormatter`, configured via `output.PrettyTableFormatterOptions`. - -- Formatter & breakpoint logic: [`pkg/output/pretty_table.go`](../../pkg/output/pretty_table.go) -- Column & option types: [`pkg/output/pretty_table_types.go`](../../pkg/output/pretty_table_types.go) -- Table rendering: [`pkg/output/pretty_table_table.go`](../../pkg/output/pretty_table_table.go) -- Card rendering: [`pkg/output/pretty_table_cards.go`](../../pkg/output/pretty_table_cards.go) -- Width/layout math: [`pkg/output/pretty_table_layout.go`](../../pkg/output/pretty_table_layout.go) - -> Reference implementations: `azd tool list` in [`cmd/tool.go`](../../cmd/tool.go), `azd extension list` in [`cmd/extension.go`](../../cmd/extension.go). Model new list commands on these. - -#### Responsive Breakpoints - -The formatter selects one of three layouts from the current terminal width (`resolveBreakpoint`). Defaults live in `pretty_table_types.go` and can be overridden per command via `FullThreshold` / `CompactThreshold`. - -| Layout | Width condition | What renders | -| ----------- | -------------------------------------- | ------------------------------------------------------------------------ | -| **Full** | `width >= 100` (`DefaultFullThreshold`) | All columns; long values may still wrap/truncate to fit | -| **Compact** | `width >= 60` (`DefaultCompactThreshold`) | Only columns with `Priority <= 2`; the dropped columns collapse into a single header-only `···` marker | -| **Card** | `width < 60` | One card per row, optionally grouped into sections | - -- Do **not** invent new width thresholds per command. Use the shared defaults unless there is a strong, documented reason to override them. -- Set `ForceCards: true` only when a command should always render cards regardless of width. - -#### Defining Columns - -Each column is a `PrettyColumn` (a `Column` plus responsive metadata). Conventions: - -- **Headings are UPPERCASE** and short (`ID`, `NAME`, `STATUS`, `INSTALLED`, `LATEST`, `SOURCE`, `CATEGORY`, `TYPE`, `LOCATION`). Reuse these standard names across commands — don't introduce a synonym like `VERSION` when `INSTALLED`/`LATEST` already express the concept. -- **`Priority`** controls compact visibility: `1` and `2` survive the compact breakpoint; `3+` are full-table only; `0` is treated as `1`. Keep the identifying columns (`ID`, `STATUS`, `INSTALLED`) at priority `1`. -- **`ColorFunc`** applies semantic color to a cell (see status colors below). Use the standard `output.With*Format` helpers, never raw ANSI. -- **`Truncatable`** shortens a value with an ellipsis (`…`), keeping at least `minVisibleChars` (5) characters. -- **`Wrappable`** lets a value wrap onto at most `maxWrapLines` (2) lines before truncating — preferred for identifier columns like `NAME`. -- **`CardTitle`** promotes the column's value to the highlighted (blue, `WithHighLightFormat`) title line of each card (no label), and excludes it from the labeled card fields. Typically set on `NAME`. -- **`CardValueTemplate`** provides an alternate template for the card layout, e.g. to omit a `LATEST` value in cards when it equals `INSTALLED`. (Cards omit empty fields entirely; they do not print a `-` placeholder.) -- **`CardGroupColumn`** (an option, not a column) groups cards under section headers by that column's value (e.g. group by `SOURCE` or `CATEGORY`). Grouping preserves the input order of rows — it does **not** sort; sort your rows before formatting. -- **`ResponsiveColumnHint: true`** enables two related behaviors: the header-only `···` placeholder column (shown only at the compact breakpoint when columns are dropped) **and** the "Showing N of M columns…" hint line (shown whenever columns are hidden *or* a value is truncated, including in the full layout). Enable it for user-facing lists. - -```go -columns := []output.PrettyColumn{ - {Column: output.Column{Heading: "ID", ValueTemplate: "{{.Id}}"}, Priority: 1}, - { - Column: output.Column{Heading: "NAME", ValueTemplate: "{{.DisplayName}}"}, - Priority: 2, - CardTitle: true, - Wrappable: true, - Truncatable: true, - }, - { - Column: output.Column{Heading: "STATUS", ValueTemplate: "{{.Status}}"}, - Priority: 1, - ColorFunc: extensionStatusColor, // semantic status color - }, -} -formatter.Format(rows, writer, output.PrettyTableFormatterOptions{ - Columns: columns, - CardGroupColumn: "SOURCE", - ResponsiveColumnHint: true, -}) -``` - -#### Header, Spacing & Status Styling - -- **Header row**: bold high-white (`color.New(color.Bold, color.FgHiWhite)`, equivalent to `WithBold`), followed by a **gray** underline rule (`WithGrayFormat`). -- **Column spacing**: a fixed `columnPadding` (3 spaces). **No box-drawing separators** and **no `tabwriter`** in list tables — `tabwriter` is reserved for the `show`/detail screens. -- **Empty values**: in the **table** layouts, render a literal `-` for a missing value (e.g. `INSTALLED` with no installed version). This is done in the column's `ValueTemplate` (e.g. `{{if .Version}}{{.Version}}{{else}}-{{end}}`), not automatically by the formatter. In the **card** layout, empty fields are omitted rather than shown as `-`. -- **Status colors** are centralized in per-command helpers (`toolStatusColor`, `extensionStatusColor`) that map a status string to a color helper. Keep these mappings consistent: - -| Status text | Meaning | Color helper | -| ------------------- | ----------------------------- | ------------------- | -| `Installed` / `Up to date` | Present and current | `WithSuccessFormat` | -| `Upgrade available` | Installed but outdated | `WithWarningFormat` | -| `Incompatible` | Cannot be used | `WithErrorFormat` | -| `Not installed` | Absent (not an error) | `WithGrayFormat` | - -#### Layout Examples - -**Full table** (`width >= 100`) — every column, gray underline rule: - -``` -ID NAME STATUS INSTALLED LATEST SOURCE -──────────────────────────────────────────────────────────────────────────────────────────────────── -az-cli Azure CLI Up to date 1.0.0 1.0.0 azd -``` - -**Compact table** (`60 <= width < 100`) — only `Priority <= 2` columns; the dropped columns collapse into a single header-only `···` column (data rows leave it blank), with a hint line: - -``` -ID NAME STATUS INSTALLED ··· -──────────────────────────────────────────────────────────────────────────────────────── -az-cli Azure CLI Up to date 1.0.0 - -Showing 4 of 5 columns. Resize the terminal or run with -o json for full details. -``` - -**Card layout** (`width < 60`) — one card per row, grouped by `CardGroupColumn`, `CardTitle` value as the heading: - -``` -── SOURCE: azd ───────────────────── - -Azure CLI -ID: az-cli -STATUS: Up to date -INSTALLED: 1.0.0 -``` - -#### List Command Guidelines - -- **Always support `-o json`.** The responsive layouts are for humans; scripts and full detail should use `--output json`. The compact hint explicitly points users there. -- **Empty state**: print a clear, single-line message instead of an empty table (e.g. `No tools found in the registry.`, `No tools found.`). Extension list adds guidance on how to add a source. -- **Stable ordering**: the formatter does not sort — it renders (and groups cards) in the order rows are passed in. **Sort your rows before formatting** and group by a stable key (`tool list` by `CATEGORY`, `extension list` by `SOURCE`) so output is deterministic. -- **Don't hand-roll a new table.** If you find yourself writing `tabwriter` for a list of items, use `PrettyTableFormatter` instead so the command inherits responsive behavior for free. - ### CLI Color Standards The CLI uses consistent color formatting through helper functions defined in [`cli/azd/pkg/output/colors.go`](../../pkg/output/colors.go). **Always use these helper functions** instead of writing raw ANSI escape codes. diff --git a/cli/azd/docs/tracing-in-azd.md b/cli/azd/docs/tracing-in-azd.md index f3dde61f29a..4b1b651cdd3 100644 --- a/cli/azd/docs/tracing-in-azd.md +++ b/cli/azd/docs/tracing-in-azd.md @@ -230,7 +230,7 @@ The first-run middleware (`cmd/middleware/tool_first_run.go`) is not registered ### Per-Operation Attributes -The `tool install` / `tool upgrade` / `tool check` / `tool show` actions emit: +The `tool install` / `tool update` / `tool check` / `tool show` actions emit: | Attribute | Type | Emitted by | Notes | | --- | --- | --- | --- | @@ -243,7 +243,7 @@ The `tool install` / `tool upgrade` / `tool check` / `tool show` actions emit: | `tool.install.failure_count` | int | Batch install / upgrade | Number of tools that failed. | | `tool.install.failed_ids` | string | At least one failure | Comma-separated **sorted** tool IDs whose operation failed. **Only tool IDs are recorded — error messages flow through the global error middleware (`error.message`).** When the batch call itself errors before any per-tool result is produced, the count of failed IDs may be **less than `failure_count`** (failures are synthesized from the requested set, but a synthesized entry with no `Tool` reference is omitted from the ID list to avoid emitting "unknown"). | | `tool.install.duration_ms` | int | Batch install / upgrade | Wall-clock duration of the operation in milliseconds. Per-tool durations are intentionally **not** emitted: the cardinality cost would be `O(tools × installs)` for limited diagnostic value, and the aggregate captures the only number actionable at fleet scale. Per-tool durations remain available in the in-process `InstallResult.Duration` for local logging / dry-run reporting. | -| `tool.upgrade.from_version` | string | Single-target upgrade | Pre-upgrade installed version. Captured via detection on both the explicit-args (`azd tool upgrade `) and auto-detect (`azd tool upgrade`) paths. Unset only when detection failed or the tool was not previously installed. | +| `tool.upgrade.from_version` | string | Single-target update | Pre-update installed version. Captured via detection on both the explicit-args (`azd tool update `) and auto-detect (`azd tool update`) paths. Unset only when detection failed or the tool was not previously installed. | | `tool.upgrade.to_version` | string | Single-target upgrade succeeded | Post-upgrade installed version. Only emitted when the upgrade succeeded — on failure `InstalledVersion` is either the unchanged pre-upgrade value or empty, which would be ambiguous against `from_version`. | | `tool.check.updates_available` | int | `tool check` | Count of tools whose `UpdateAvailable` is `true`. | diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local.go index 05f10440b32..320a6cc2569 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local.go @@ -234,7 +234,7 @@ func newCheckGRPCAndVersion(deps Dependencies) Check { Message: fmt.Sprintf( "Extension version %s is older than %s; the new hosted-agents backend requires the floor.", ver, MinNewBackendVersion), - Suggestion: "Upgrade with `azd ext upgrade azure.ai.agents`.", + Suggestion: "Update with `azd ext update azure.ai.agents`.", Links: []string{"https://aka.ms/hostedagents/tsg/readme"}, Details: map[string]any{ "extensionVersion": ver, diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local_test.go index 3a05a0bf529..f770df2a2d2 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_local_test.go @@ -165,7 +165,7 @@ func TestCheckGRPCAndVersion_BelowFloor_Warns(t *testing.T) { require.Equal(t, StatusWarn, got.Status) require.Contains(t, got.Message, "0.1.26-preview") require.Contains(t, got.Message, MinNewBackendVersion) - require.Contains(t, got.Suggestion, "azd ext upgrade azure.ai.agents") + require.Contains(t, got.Suggestion, "azd ext update azure.ai.agents") require.Contains(t, got.Links, "https://aka.ms/hostedagents/tsg/readme") require.Equal(t, "0.1.26-preview", got.Details["extensionVersion"]) require.Equal(t, MinNewBackendVersion, got.Details["minBackendVersion"]) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go index 884ca71f428..bba06185dca 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -185,7 +185,7 @@ func checkAiModelServiceAvailable(ctx context.Context, azdClient *azdext.AzdClie return exterrors.Compatibility( exterrors.CodeIncompatibleAzdVersion, "this version of the azure.ai.agents extension is incompatible with your installed version of azd.", - "upgrade azd to the latest version (https://aka.ms/azd/upgrade) and retry", + "update azd to the latest version (https://aka.ms/azd/upgrade) and retry", ) } diff --git a/cli/azd/extensions/azure.coding-agent/README.md b/cli/azd/extensions/azure.coding-agent/README.md index 995f8c00026..80591d3f8d8 100644 --- a/cli/azd/extensions/azure.coding-agent/README.md +++ b/cli/azd/extensions/azure.coding-agent/README.md @@ -29,7 +29,7 @@ azd extension install azure.coding-agent Or, if you already the `azure.coding-agent` extension installed, and you want to upgrade to the latest version: ```shell -azd extension upgrade azure.coding-agent +azd extension update azure.coding-agent ``` ## Usage diff --git a/cli/azd/internal/figspec/customizations.go b/cli/azd/internal/figspec/customizations.go index 47917592b20..ec7816afd80 100644 --- a/cli/azd/internal/figspec/customizations.go +++ b/cli/azd/internal/figspec/customizations.go @@ -92,7 +92,7 @@ func (c *Customizations) GetCommandArgGenerator(ctx *CommandContext, argName str if argName == "extension-id" { return FigGenListExtensions } - case "azd extension upgrade", "azd extension uninstall": + case "azd extension update", "azd extension uninstall": if argName == "extension-id" { return FigGenListInstalledExtensions } diff --git a/cli/azd/internal/figspec/fig_generators_test.go b/cli/azd/internal/figspec/fig_generators_test.go index 75a58f094e4..517faab4798 100644 --- a/cli/azd/internal/figspec/fig_generators_test.go +++ b/cli/azd/internal/figspec/fig_generators_test.go @@ -28,7 +28,7 @@ func TestCustomizations_GetCommandArgGenerator(t *testing.T) { // install is handled via GetCommandArgs (combined id|zip arg), not here. {"ext_install", "azd extension install", "extension-id", ""}, {"ext_show", "azd extension show", "extension-id", FigGenListExtensions}, - {"ext_upgrade", "azd extension upgrade", "extension-id", FigGenListInstalledExtensions}, + {"ext_update", "azd extension update", "extension-id", FigGenListInstalledExtensions}, {"ext_uninstall", "azd extension uninstall", "extension-id", FigGenListInstalledExtensions}, {"config_get", "azd config get", "path", FigGenListConfigKeys}, {"config_set", "azd config set", "path", FigGenListConfigKeys}, diff --git a/cli/azd/internal/tracing/fields/fields.go b/cli/azd/internal/tracing/fields/fields.go index f59a0bf8cd0..4493d266f63 100644 --- a/cli/azd/internal/tracing/fields/fields.go +++ b/cli/azd/internal/tracing/fields/fields.go @@ -525,7 +525,7 @@ var ( } // ToolDryRunKey records whether `--dry-run` was specified for an - // `azd tool install` or `azd tool upgrade` invocation. + // `azd tool install` or `azd tool update` invocation. ToolDryRunKey = AttributeKey{ Key: attribute.Key("tool.dry_run"), Classification: SystemMetadata, @@ -590,7 +590,7 @@ var ( // ToolFirstRunInstallSuccessCountKey mirrors ToolInstallSuccessCountKey // but is emitted only from the first-run middleware, so the user's - // subsequent `azd tool install` / `azd tool upgrade` command (which + // subsequent `azd tool install` / `azd tool update` command (which // emits its own `tool.install.success_count`) does not overwrite the // first-run signal on the same span. ToolFirstRunInstallSuccessCountKey = AttributeKey{ diff --git a/cli/azd/pkg/extensions/registry_version.go b/cli/azd/pkg/extensions/registry_version.go index 5b4a53603b5..69867be1252 100644 --- a/cli/azd/pkg/extensions/registry_version.go +++ b/cli/azd/pkg/extensions/registry_version.go @@ -28,15 +28,15 @@ func (e *ErrUnsupportedRegistrySchema) Error() string { } // NewUnsupportedRegistrySchemaError wraps an ErrUnsupportedRegistrySchema in an -// ErrorWithSuggestion that guides the user to upgrade azd. +// ErrorWithSuggestion that guides the user to update azd. func NewUnsupportedRegistrySchemaError(schemaErr *ErrUnsupportedRegistrySchema) error { return &errorhandler.ErrorWithSuggestion{ Err: schemaErr, Message: schemaErr.Error(), - Suggestion: "Upgrade azd to the latest version to use this registry", + Suggestion: "Update azd to the latest version to use this registry", Links: []errorhandler.ErrorLink{{ URL: "https://aka.ms/azd/install", - Title: "Install/upgrade azd", + Title: "Install/update azd", }}, } } diff --git a/cli/azd/pkg/extensions/registry_version_test.go b/cli/azd/pkg/extensions/registry_version_test.go index 6bc61055c50..28163617a68 100644 --- a/cli/azd/pkg/extensions/registry_version_test.go +++ b/cli/azd/pkg/extensions/registry_version_test.go @@ -334,7 +334,7 @@ func TestNewUnsupportedRegistrySchemaError(t *testing.T) { suggestionErr, ok := errors.AsType[*errorhandler.ErrorWithSuggestion](err) require.True(t, ok) require.Equal(t, schemaErr.Error(), suggestionErr.Message) - require.Contains(t, suggestionErr.Suggestion, "Upgrade azd") + require.Contains(t, suggestionErr.Suggestion, "Update azd") require.Len(t, suggestionErr.Links, 1) require.Equal(t, "https://aka.ms/azd/install", suggestionErr.Links[0].URL) } diff --git a/cli/azd/pkg/extensions/update_checker.go b/cli/azd/pkg/extensions/update_checker.go index a1da3316c68..787bef8ac34 100644 --- a/cli/azd/pkg/extensions/update_checker.go +++ b/cli/azd/pkg/extensions/update_checker.go @@ -126,10 +126,10 @@ func FormatUpdateWarning(result *UpdateCheckResult) *ux.WarningMessage { ), HidePrefix: false, Hints: []string{ - fmt.Sprintf("To upgrade: %s", - output.WithHighLightFormat("azd extension upgrade %s", result.ExtensionId)), - fmt.Sprintf("To upgrade all: %s", - output.WithHighLightFormat("azd extension upgrade --all")), + fmt.Sprintf("To update: %s", + output.WithHighLightFormat("azd extension update %s", result.ExtensionId)), + fmt.Sprintf("To update all: %s", + output.WithHighLightFormat("azd extension update --all")), }, } } diff --git a/cli/azd/pkg/extensions/update_checker_test.go b/cli/azd/pkg/extensions/update_checker_test.go index 3b66f1d8603..590cc2ecbdc 100644 --- a/cli/azd/pkg/extensions/update_checker_test.go +++ b/cli/azd/pkg/extensions/update_checker_test.go @@ -153,8 +153,8 @@ func Test_FormatUpdateWarning(t *testing.T) { require.Contains(t, warning.Description, "2.0.0") require.False(t, warning.HidePrefix) require.Len(t, warning.Hints, 2) - require.Contains(t, warning.Hints[0], "azd extension upgrade test.extension") - require.Contains(t, warning.Hints[1], "azd extension upgrade --all") + require.Contains(t, warning.Hints[0], "azd extension update test.extension") + require.Contains(t, warning.Hints[1], "azd extension update --all") } func Test_FormatUpdateWarning_NoDisplayName(t *testing.T) { diff --git a/cli/azd/pkg/extensions/update_integration_test.go b/cli/azd/pkg/extensions/update_integration_test.go index fdc3e4a914c..85400ea5da5 100644 --- a/cli/azd/pkg/extensions/update_integration_test.go +++ b/cli/azd/pkg/extensions/update_integration_test.go @@ -166,8 +166,8 @@ func Test_Integration_UpdateCheck_FullFlow(t *testing.T) { require.Contains(t, warning.Description, "2.0.0") require.False(t, warning.HidePrefix) require.Len(t, warning.Hints, 2) - require.Contains(t, warning.Hints[0], "azd extension upgrade test.extension") - require.Contains(t, warning.Hints[1], "azd extension upgrade --all") + require.Contains(t, warning.Hints[0], "azd extension update test.extension") + require.Contains(t, warning.Hints[1], "azd extension update --all") }) } diff --git a/cli/azd/pkg/output/ux/warning_test.go b/cli/azd/pkg/output/ux/warning_test.go index 9f7b9eb911e..629b637eb63 100644 --- a/cli/azd/pkg/output/ux/warning_test.go +++ b/cli/azd/pkg/output/ux/warning_test.go @@ -47,8 +47,8 @@ func TestWarningMessage_ToString_WithHints(t *testing.T) { Description: "Extension update available", HidePrefix: false, Hints: []string{ - "To upgrade: azd extension upgrade test.ext", - "To upgrade all: azd extension upgrade --all", + "To update: azd extension update test.ext", + "To update all: azd extension update --all", }, } @@ -56,8 +56,8 @@ func TestWarningMessage_ToString_WithHints(t *testing.T) { require.Contains(t, result, "WARNING:") require.Contains(t, result, "Extension update available") require.Contains(t, result, "•") - require.Contains(t, result, "To upgrade: azd extension upgrade test.ext") - require.Contains(t, result, "To upgrade all: azd extension upgrade --all") + require.Contains(t, result, "To update: azd extension update test.ext") + require.Contains(t, result, "To update all: azd extension update --all") } func TestWarningMessage_ToString_WithHintsAndIndentation(t *testing.T) { @@ -119,8 +119,8 @@ func TestWarningMessage_MarshalJSON_WithHints(t *testing.T) { Description: "Extension update available", HidePrefix: false, Hints: []string{ - "To upgrade: azd extension upgrade test.ext", - "To upgrade all: azd extension upgrade --all", + "To update: azd extension update test.ext", + "To update all: azd extension update --all", }, } @@ -129,8 +129,8 @@ func TestWarningMessage_MarshalJSON_WithHints(t *testing.T) { jsonStr := string(data) require.Contains(t, jsonStr, "WARNING:") require.Contains(t, jsonStr, "Extension update available") - require.Contains(t, jsonStr, "To upgrade: azd extension upgrade test.ext") - require.Contains(t, jsonStr, "To upgrade all: azd extension upgrade --all") + require.Contains(t, jsonStr, "To update: azd extension update test.ext") + require.Contains(t, jsonStr, "To update all: azd extension update --all") } func TestWarningMessage_MarshalJSON_EmptyHints(t *testing.T) { diff --git a/cli/azd/pkg/tool/detector.go b/cli/azd/pkg/tool/detector.go index f389437f51d..1642b1fe5a5 100644 --- a/cli/azd/pkg/tool/detector.go +++ b/cli/azd/pkg/tool/detector.go @@ -512,7 +512,7 @@ func (d *detector) detectSkill( // DetectSkillAgents returns every configured SkillAgent the skill is // currently installed through (with the version installed via each), in // manifest order. It probes every agent so callers can act on every -// install — e.g. `azd tool upgrade` refreshing the skill on each agent it +// install — e.g. `azd tool update` refreshing the skill on each agent it // was installed to, or per-agent install verification. detectSkill // delegates to it and reports the first matched agent as the aggregate // Installed/InstalledVersion. diff --git a/cli/azd/pkg/tool/installer.go b/cli/azd/pkg/tool/installer.go index 14d7826c6a8..f3d04495c00 100644 --- a/cli/azd/pkg/tool/installer.go +++ b/cli/azd/pkg/tool/installer.go @@ -1150,13 +1150,13 @@ func (i *installer) run( } verb := "Installing" if upgrade { - verb = "Upgrading" + verb = "Updating" } title := fmt.Sprintf("%s %s", verb, tool.Name) cfg.renderer.ShowSpinner(ctx, title, input.Step) result, err := i.runToolInstall(ctx, tool, upgrade) // On a successful upgrade, append the resulting version to the result - // line, mirroring skills — e.g. "Upgrading Azure CLI (v2.0.0)". + // line, mirroring skills — e.g. "Updating Azure CLI (v2.0.0)". doneTitle := title if upgrade && err == nil && result != nil && result.Success && result.InstalledVersion != "" { doneTitle = fmt.Sprintf("%s (v%s)", title, result.InstalledVersion) @@ -1404,7 +1404,7 @@ func (i *installer) runSkill( // latest, which the result line reports. verb := "Installing" if upgrade { - verb = "Upgrading" + verb = "Updating" } var ( succeeded []string @@ -1542,7 +1542,7 @@ func (i *installer) resolveSkillTargets( continue } fmt.Fprintln(os.Stderr, output.WithWarningFormat( - "Skipping upgrade for %s: %s is not installed on it.", + "Skipping update for %s: %s is not installed on it.", agent.DisplayName, tool.Name, )) } @@ -1553,7 +1553,7 @@ func (i *installer) resolveSkillTargets( } return nil, fmt.Errorf( "%s is not installed on any available agent (%s); "+ - "nothing to upgrade", + "nothing to update", tool.Name, strings.Join(onPathNames, ", "), ) } @@ -1588,7 +1588,7 @@ func (i *installer) resolveSkillTargets( "%s is not installed on any available agent", tool.Name, ), - Message: "Cannot upgrade " + tool.Name, + Message: "Cannot update " + tool.Name, Suggestion: fmt.Sprintf( "%s is not installed yet. Install it first:\n\n"+ " azd tool install %s", diff --git a/cli/azd/pkg/tool/installer_test.go b/cli/azd/pkg/tool/installer_test.go index f0a9068ec03..00f8bd24317 100644 --- a/cli/azd/pkg/tool/installer_test.go +++ b/cli/azd/pkg/tool/installer_test.go @@ -248,7 +248,7 @@ func TestRunToolInstall_StepProgress(t *testing.T) { // TestRunToolUpgrade_StepProgress_ShowsVersion verifies that a non-skill // upgrade appends the resulting version to the step result line — the same -// treatment skills get — e.g. "Upgrading Test Tool (v2.64.0)". +// treatment skills get — e.g. "Updating Test Tool (v2.64.0)". func TestRunToolUpgrade_StepProgress_ShowsVersion(t *testing.T) { t.Parallel() @@ -290,8 +290,8 @@ func TestRunToolUpgrade_StepProgress_ShowsVersion(t *testing.T) { require.NoError(t, err) require.True(t, result.Success, "upgrade must succeed; err=%v", result.Error) - assert.Equal(t, []string{"Upgrading Test Tool"}, r.starts) - assert.Equal(t, []string{"Upgrading Test Tool (v2.64.0)"}, r.stops, + assert.Equal(t, []string{"Updating Test Tool"}, r.starts) + assert.Equal(t, []string{"Updating Test Tool (v2.64.0)"}, r.stops, "a non-skill upgrade must report the resulting version, like skills") } @@ -1508,8 +1508,8 @@ func TestRunSkill_Upgrade_StepResultShowsVersion(t *testing.T) { assert.False(t, result.AlreadyUpToDate, "an actual upgrade is not up-to-date") // Spinner title has no version; the result line appends the new version. - assert.Equal(t, []string{"Upgrading Test Azure Skills in copilot"}, r.starts) - assert.Equal(t, []string{"Upgrading Test Azure Skills in copilot (v1.1.86)"}, r.stops) + assert.Equal(t, []string{"Updating Test Azure Skills in copilot"}, r.starts) + assert.Equal(t, []string{"Updating Test Azure Skills in copilot (v1.1.86)"}, r.stops) } // TestRunSkill_Upgrade_AlreadyUpToDate verifies that when the agent reports the @@ -1549,7 +1549,7 @@ func TestRunSkill_Upgrade_AlreadyUpToDate(t *testing.T) { require.True(t, result.Success, "upgrade must succeed; err=%v", result.Error) assert.True(t, result.AlreadyUpToDate, "nothing changed, so already up to date") - assert.Equal(t, []string{"Upgrading Test Azure Skills in copilot"}, r.starts) + assert.Equal(t, []string{"Updating Test Azure Skills in copilot"}, r.starts) assert.Equal(t, []string{"Test Azure Skills in copilot is already up to date (v1.1.86)."}, r.stops) } @@ -1594,7 +1594,7 @@ func TestRunSkill_Upgrade_DetectionLag_NotUpToDate(t *testing.T) { require.True(t, result.Success, "upgrade must succeed; err=%v", result.Error) assert.False(t, result.AlreadyUpToDate, "a reported upgrade must not be marked up-to-date even when detection lags") - assert.Equal(t, []string{"Upgrading Test Azure Skills in copilot (v2.0.0)"}, r.stops) + assert.Equal(t, []string{"Updating Test Azure Skills in copilot (v2.0.0)"}, r.stops) } // TestRunSkill_OutputPrintedBelowStep verifies that when the agent CLI writes @@ -2320,12 +2320,12 @@ func TestRunSkill_Upgrade_PrintsPerAgentHeader(t *testing.T) { }) require.True(t, result.Success, "result.Error=%v", result.Error) - assert.Contains(t, stderr, "Upgrading Test Azure Skills in copilot") - assert.Contains(t, stderr, "Upgrading Test Azure Skills in claude") + assert.Contains(t, stderr, "Updating Test Azure Skills in copilot") + assert.Contains(t, stderr, "Updating Test Azure Skills in claude") } // TestRunSkill_Upgrade_NoAgent_NotInstalled_ReturnsInstallGuidance -// verifies that `azd tool upgrade ` with no --agent, when the +// verifies that `azd tool update ` with no --agent, when the // skill is installed on no available agent, returns a clear "install // first" guidance error instead of falling through to an agent and // attempting to update a plugin that was never installed (which used to diff --git a/cli/azd/pkg/tools/bicep/bicep.go b/cli/azd/pkg/tools/bicep/bicep.go index 1982be30c13..19a6197058d 100644 --- a/cli/azd/pkg/tools/bicep/bicep.go +++ b/cli/azd/pkg/tools/bicep/bicep.go @@ -123,7 +123,7 @@ func (cli *Cli) ensureInstalled(ctx context.Context) error { log.Printf("installed bicep version %s is older than %s; updating.", ver.String(), Version.String()) if err := runStep( - ctx, cli.console, "Upgrading Bicep", func() error { + ctx, cli.console, "Updating Bicep", func() error { return downloadBicep(ctx, cli.transporter, Version, bicepPath) }, ); err != nil { diff --git a/cli/azd/pkg/tools/bicep/bicep_test.go b/cli/azd/pkg/tools/bicep/bicep_test.go index d59a08d5f26..7dfcffb8d37 100644 --- a/cli/azd/pkg/tools/bicep/bicep_test.go +++ b/cli/azd/pkg/tools/bicep/bicep_test.go @@ -130,13 +130,13 @@ func TestNewBicepCliWillUpgrade(t *testing.T) { require.Equal(t, mockinput.SpinnerOp{ Op: mockinput.SpinnerOpShow, - Message: "Upgrading Bicep", + Message: "Updating Bicep", Format: input.Step, }, mockContext.Console.SpinnerOps()[0]) require.Equal(t, mockinput.SpinnerOp{ Op: mockinput.SpinnerOpStop, - Message: "Upgrading Bicep", + Message: "Updating Bicep", Format: input.StepDone, }, mockContext.Console.SpinnerOps()[1]) diff --git a/cli/azd/pkg/tools/pack/pack.go b/cli/azd/pkg/tools/pack/pack.go index 2f4f98f5912..931c04ce2f2 100644 --- a/cli/azd/pkg/tools/pack/pack.go +++ b/cli/azd/pkg/tools/pack/pack.go @@ -167,12 +167,12 @@ func (cli *Cli) ensureInstalled(ctx context.Context) error { if ver.LT(Version) { log.Printf("installed pack version %s is older than %s; updating.", ver.String(), Version.String()) - msg := "Upgrading pack" + msg := "Updating pack" cli.console.ShowSpinner(ctx, msg, input.Step) err := downloadPack(ctx, cli.transporter, Version, cli.extract, cliPath) cli.console.StopSpinner(ctx, "", input.Step) if err != nil { - return fmt.Errorf("upgrading pack: %w", err) + return fmt.Errorf("updating pack: %w", err) } } diff --git a/cli/azd/resources/error_suggestions.yaml b/cli/azd/resources/error_suggestions.yaml index 7b7db9155cf..a71a3a4ad20 100644 --- a/cli/azd/resources/error_suggestions.yaml +++ b/cli/azd/resources/error_suggestions.yaml @@ -601,10 +601,10 @@ rules: # catch-all for any alternative code paths where the error surfaces unwrapped. - errorType: "ErrUnsupportedRegistrySchema" message: "The extension registry uses a schema version not supported by this version of azd." - suggestion: "Upgrade azd to the latest version to use this registry." + suggestion: "Update azd to the latest version to use this registry." links: - url: "https://aka.ms/azd/install" - title: "Install/upgrade azd" + title: "Install/update azd" # ============================================================================ # Text Pattern Rules — Broad/generic patterns (least specific, must be last) diff --git a/docs/reference/telemetry-data.md b/docs/reference/telemetry-data.md index 202e8cce7d5..44f20260737 100644 --- a/docs/reference/telemetry-data.md +++ b/docs/reference/telemetry-data.md @@ -707,7 +707,7 @@ How to find telemetry for a given feature area. Start here if you know the featu | **Self-Update** | `cmd.update` | `update.installMethod`, `update.fromVersion` | Update adoption | | **Hooks** | `hooks.exec` | `hooks.name`, `hooks.type`, `hooks.kind` | Hook usage by type | | **Container Build** | `container.publish`, `container.remotebuild`, `tools.pack.build` | `pack.builder.image` | Build method usage, success rates | -| **Tool Management (`azd tool`)** | `cmd.tool.install`, `cmd.tool.upgrade`, `cmd.tool.uninstall`, `cmd.tool.check` | `tool.id`, `tool.install.strategy` | Install/upgrade/uninstall success, upgrade availability | +| **Tool Management (`azd tool`)** | `cmd.tool.install`, `cmd.tool.update`, `cmd.tool.uninstall`, `cmd.tool.check` | `tool.id`, `tool.install.strategy` | Install/update/uninstall success, update availability | ## See Also diff --git a/docs/specs/metrics-audit/feature-telemetry-matrix.md b/docs/specs/metrics-audit/feature-telemetry-matrix.md index 024971b2d32..8a11e5e73cb 100644 --- a/docs/specs/metrics-audit/feature-telemetry-matrix.md +++ b/docs/specs/metrics-audit/feature-telemetry-matrix.md @@ -32,7 +32,7 @@ These commands emit attributes or events beyond the global middleware span. | `update` | `update.installMethod`, `update.channel`, `update.fromVersion`, `update.toVersion`, `update.result` | Result codes cover success, failure, and skip reasons | | Extensions (dynamic) | `extension.id`, `extension.version`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.source.kind`, `extension.source.from`, `extension.source.to`, `extension.dependency_of`, `extension.dependency_upgrade_count`, `extension.upgrade.outcome`, `extension.upgrade.duration_ms` + trace-context propagation to child process | Covers `ext.run`, `ext.install`, `ext.upgrade`, `ext.promote` events; `extension.source.kind` distinguishes no source, registered source, and direct location usage for extension list/show/install/upgrade | | `mcp start` | Per-tool spans via `tracing.Start` with `mcp.client.name`, `mcp.client.version` | MCP event prefix `mcp.*` | -| `tool install` / `tool upgrade` / `tool uninstall` / `tool check` / `tool list` / `tool show` | `tool.id`, `tool.ids`, `tool.dry_run`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.upgrade.from_version`, `tool.upgrade.to_version`, `tool.check.updates_available` | Comprehensive coverage in `cli/azd/cmd/tool.go`; install/upgrade emit `tools.pack.build` spans for pack-based tools | +| `tool install` / `tool update` / `tool uninstall` / `tool check` / `tool list` / `tool show` | `tool.id`, `tool.ids`, `tool.dry_run`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.upgrade.from_version`, `tool.upgrade.to_version`, `tool.check.updates_available` | Comprehensive coverage in `cli/azd/cmd/tool.go`; install/update emit `tools.pack.build` spans for pack-based tools | | `copilot` (agent) | `copilot.initialize` event (model + reasoning config), `copilot.session` event (session create/resume) | Emitted from `internal/agent/copilot_agent.go`; covers the experimental copilot agent surface | | `provision` | `validation.provision` event (provision validation outcome + 6 fields), 8 `arm.*` events (subscription / resource-group deploy / stack-deploy / what-if / validate), `aks.postprovision.skip`, per-layer `provision.layer.*` counts (`count`, `max_parallel`, `safe_fallback_count`, `explicit_dependson_count`) when multi-layer infra is used | Telemetry added across `internal/cmd/provision_*.go` and the ARM deployment client | | `deploy` / `publish` / `package` | `deploy.appservice.zip` event (zip-deploy outcome), `container.credentials` / `container.publish` / `container.remotebuild` events for container-based services | Per-service-target instrumentation; container events emitted from container-app and ACR push paths | @@ -87,8 +87,8 @@ These commands emit attributes or events beyond the global middleware span. | **Copilot Consent** | | | | | | | `copilot consent` | `list`, `revoke`, `grant` | ✅ | ❌ | ❌ | Low priority | | **Extension Management** | | | | | | -| `extension` | `list`, `show`, `install`, `uninstall`, `upgrade` | ✅ | ✅ | ✅ | Covered by `extension.*` fields and `ext.install`, `ext.upgrade`, `ext.promote` events; `extension.source.kind` tracks `--source` argument kind for list/show/install/upgrade | -| `extension source` | `list`, `add`, `remove`, `validate` | ✅ | ❌ | ❌ | Subcommand name in the global span captures the operation; `extension.source*` attributes are recorded by `extension upgrade` / `extension promote`, not by this subcommand | +| `extension` | `list`, `show`, `install`, `uninstall`, `update` | ✅ | ✅ | ✅ | Covered by `extension.*` fields and `ext.install`, `ext.upgrade`, `ext.promote` events; `extension.source.kind` tracks `--source` argument kind for list/show/install/update | +| `extension source` | `list`, `add`, `remove`, `validate` | ✅ | ❌ | ❌ | Subcommand name in the global span captures the operation; `extension.source*` attributes are recorded by `extension update` / `extension promote`, not by this subcommand | | **Init** | | | | | | | `init` | — | ✅ | ✅ | ✅ | Comprehensive coverage via `appinit.*` fields | | **Update** | | | | | | @@ -98,7 +98,7 @@ These commands emit attributes or events beyond the global middleware span. | **Tool Management** | | | | | | | `tool list` | — | ✅ | ✅ | ❌ | `tool.ids` listed for visibility into per-row outputs | | `tool install` | — | ✅ | ✅ | ✅ | `tool.id`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.dry_run`; `tools.pack.build` for pack-based tools | -| `tool upgrade` | — | ✅ | ✅ | ✅ | All `tool.install.*` plus `tool.upgrade.from_version`, `tool.upgrade.to_version` | +| `tool update` | — | ✅ | ✅ | ✅ | All `tool.install.*` plus `tool.upgrade.from_version`, `tool.upgrade.to_version` | | `tool uninstall` | — | ✅ | ✅ | ✅ | `tool.id`/`tool.ids`, `tool.dry_run`, and the `tool.install.*` aggregate/per-tool fields (no version fields) | | `tool check` | — | ✅ | ✅ | ❌ | `tool.check.updates_available` (count) | | `tool show` | — | ✅ | ✅ | ❌ | `tool.id` | @@ -124,8 +124,8 @@ command-specific telemetry fields provide analytical value beyond the command na | Pipeline auth | `pipeline.auth` | `pipeline config` | Distinguishes federated vs client-credentials | | Infra provider | `infra.provider` | `infra generate`, `infra synth`, `provision`, `up`, `down` | provision/up/down: sorted, de-duplicated string slice of resolved providers — `bicep`/`terraform`/`arm`/`pulumi` verbatim, `custom` for extension providers (raw name not emitted); multi-layer projects that combine providers record each distinct value (e.g. `["bicep","terraform"]`). `infra generate`/`synth`: the value read from azure.yaml's `infra.provider` emitted directly as a single string (`bicep`/`terraform`/`arm`/`pulumi`, `auto` when unset, or `custom` for extension providers — raw name not emitted) | | Tool ID | `tool.id` / `tool.ids` | `tool *` | Identifies which managed tool (e.g., bicep, gh, kubectl) the command acted on | -| Tool install metrics | `tool.install.*` | `tool install`, `tool upgrade`, `tool uninstall` | Success count, failure count, duration, strategy — quantitative install health | -| Tool upgrade versions | `tool.upgrade.from_version`, `tool.upgrade.to_version` | `tool upgrade` | Tracks adoption of new tool versions | +| Tool install metrics | `tool.install.*` | `tool install`, `tool update`, `tool uninstall` | Success count, failure count, duration, strategy — quantitative install health | +| Tool update versions | `tool.upgrade.from_version`, `tool.upgrade.to_version` | `tool update` | Tracks adoption of new tool versions | | Provision validation outcome | `validation.provision.outcome` (+ peer fields incl. `validation.provision.check_type`) | `provision` | Distinguishes passed / warnings-accepted / canceled local validation; `check_type` separates the provider-agnostic `provision` dispatch from the Bicep `arm-provision` dispatch (both share the event) | | ARM deployment events | `arm.deploy.*`, `arm.stack.deploy.*`, `arm.whatif.*`, `arm.validate.*` | `provision` | Distinguishes deployment scope (subscription vs resource-group) and operation kind (deploy / stack / what-if / validate) | | Container events | `container.credentials`, `container.publish`, `container.remotebuild` | `package`, `deploy` | Per-stage container lifecycle for container-based services | diff --git a/docs/specs/metrics-audit/telemetry-schema.md b/docs/specs/metrics-audit/telemetry-schema.md index 402e4de9f73..2fa1b8a66cc 100644 --- a/docs/specs/metrics-audit/telemetry-schema.md +++ b/docs/specs/metrics-audit/telemetry-schema.md @@ -17,7 +17,7 @@ OpenTelemetry span name or event name. | `AgentTroubleshootEvent` | `agent.troubleshoot` | Agent troubleshooting event | | `ExtensionRunEvent` | `ext.run` | Extension execution event | | `ExtensionInstallEvent` | `ext.install` | Extension install/upgrade event | -| `ExtensionUpgradeEvent` | `ext.upgrade` | Single extension upgrade attempt | +| `ExtensionUpgradeEvent` | `ext.upgrade` | Single extension update attempt | | `ExtensionPromoteEvent` | `ext.promote` | Extension registry promotion (e.g., dev → main) | | `CopilotInitializeEvent` | `copilot.initialize` | Copilot initialization event | | `CopilotSessionEvent` | `copilot.session` | Copilot session lifecycle event | From 5563f1c7fb0d5b7191b12ec5f9d333e6f2be2787 Mon Sep 17 00:00:00 2001 From: hyoshis Date: Mon, 3 Aug 2026 10:32:13 -0700 Subject: [PATCH 05/10] Fix update rename review findings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/cmd/tool.go | 6 ++- cli/azd/cmd/tool_test.go | 34 +++++++++++++++++ .../extension-resolution-and-versioning.md | 12 +++--- cli/azd/docs/tracing-in-azd.md | 32 ++++++++-------- cli/azd/pkg/tools/bicep/bicep.go | 4 +- docs/reference/telemetry-data.md | 38 +++++++++---------- 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/cli/azd/cmd/tool.go b/cli/azd/cmd/tool.go index 59279da4e99..cd05f806fad 100644 --- a/cli/azd/cmd/tool.go +++ b/cli/azd/cmd/tool.go @@ -1524,7 +1524,7 @@ func (a *toolUpgradeAction) resolveAgentOptions( } // dryRun detects the current status of the tools and displays what -// the upgrade command would do without making changes. +// the update command would do without making changes. func (a *toolUpgradeAction) dryRun( ctx context.Context, tools []*tool.ToolDefinition, @@ -1559,7 +1559,9 @@ func (a *toolUpgradeAction) dryRun( // Keep the existing machine-readable action value stable for callers // using either the update command or its upgrade alias. for i := range rows { - rows[i].Action = "upgrade" + if rows[i].Action == "update" { + rows[i].Action = "upgrade" + } } return nil, a.formatter.Format(rows, a.writer, nil) } diff --git a/cli/azd/cmd/tool_test.go b/cli/azd/cmd/tool_test.go index 3dc25498597..5e44b734a40 100644 --- a/cli/azd/cmd/tool_test.go +++ b/cli/azd/cmd/tool_test.go @@ -618,6 +618,40 @@ func TestToolInstallAction_DryRun_MultiTool_EmitsSortedToolIds(t *testing.T) { assert.False(t, ok, "tool.id must NOT be emitted alongside tool.ids (mutual exclusion)") } +func TestToolUpgradeAction_DryRun_JsonPreservesSkippedAction(t *testing.T) { + tracing.ResetUsageAttributesForTest() + + detector := &cmdMockDetector{ + detectTool: func(_ context.Context, td *tool.ToolDefinition) (*tool.ToolStatus, error) { + return &tool.ToolStatus{ + Tool: td, + Installed: td.Id == "az-cli", + InstalledVersion: "1.0.0", + }, nil + }, + } + manager := tool.NewManager(detector, &cmdMockInstaller{}, nil) + + var buf bytes.Buffer + action := newToolUpgradeAction( + []string{"az-cli", "github-copilot-cli"}, + &toolUpgradeFlags{dryRun: true}, + manager, + mockinput.NewMockConsole(), + &output.JsonFormatter{}, + &buf, + ) + + _, err := action.Run(t.Context()) + require.NoError(t, err) + + var items []toolDryRunItem + require.NoError(t, json.Unmarshal(buf.Bytes(), &items)) + require.Len(t, items, 2) + assert.Equal(t, "upgrade", items[0].Action) + assert.Equal(t, "skip (not installed)", items[1].Action) +} + // TestToolInstallAction_AllFailureBatch_EmitsCorrectAggregates exercises the // install path end-to-end against a mock Installer that fails every per-tool // install. It verifies the aggregate telemetry contract: diff --git a/cli/azd/docs/extensions/extension-resolution-and-versioning.md b/cli/azd/docs/extensions/extension-resolution-and-versioning.md index 60fe3d2ed20..c0b2c53159d 100644 --- a/cli/azd/docs/extensions/extension-resolution-and-versioning.md +++ b/cli/azd/docs/extensions/extension-resolution-and-versioning.md @@ -139,8 +139,8 @@ When `azd` resolves versions, it filters them into compatible and incompatible s ### Behavior - `azd` filters out all versions whose `requiredAzdVersion` constraint is not satisfied by the running `azd` version, then selects the **highest remaining compatible version** that also matches the user's version constraint. -- If a **newer incompatible version** exists beyond the selected version, `azd` shows a **warning** suggesting the user upgrade `azd`. -- If **no compatible versions** remain after filtering, the install **fails** with guidance to upgrade `azd`. The install also fails if the user explicitly requests a specific version that is incompatible. +- If a **newer incompatible version** exists beyond the selected version, `azd` shows a **warning** suggesting the user update `azd`. +- If **no compatible versions** remain after filtering, the install **fails** with guidance to update `azd`. The install also fails if the user explicitly requests a specific version that is incompatible. - If `requiredAzdVersion` is **empty or cannot be parsed**, the version is treated as compatible (fail-open). This ensures that extensions without the field remain installable. ## Install Flow @@ -166,10 +166,10 @@ Once a version is resolved, installation proceeds through these steps: When the source is **not** changing (same source as the installed extension): - **Same version** — a no-op; the install is skipped. -- **Newer version** — upgraded in place. +- **Newer version** — updated in place. - **Older version** — a downgrade; `azd` **prompts for confirmation** before replacing the newer install with an older one. Declining skips the install. In `--no-prompt` mode `azd` skips with guidance to pass `--force`, and `--force` proceeds without prompting. -When the source **is** changing (for example installing a bundle build over a registry build, or vice versa), the artifacts may differ, so `azd` does not silently proceed, no-op, or block a downgrade. Instead it **prompts for confirmation** before replacing the installed extension. The prompt states the version transition explicitly — *Reinstall*, *Upgrade to ``*, or *Downgrade to ``* — and the target source. Declining skips the install; confirming reinstalls and re-points the extension to the new source. In `--no-prompt` mode `azd` skips with guidance to pass `--force`, and `--force` proceeds without prompting. +When the source **is** changing (for example installing a bundle build over a registry build, or vice versa), the artifacts may differ, so `azd` does not silently proceed, no-op, or block a downgrade. Instead it **prompts for confirmation** before replacing the installed extension. The prompt states the version transition explicitly — *Reinstall*, *Update to ``*, or *Downgrade to ``* — and the target source. Declining skips the install; confirming reinstalls and re-points the extension to the new source. In `--no-prompt` mode `azd` skips with guidance to pass `--force`, and `--force` proceeds without prompting. Because each bundle install registers a unique transient source, installing from **any** bundle over an already-installed extension is always treated as a source change — so it prompts even when the bundled version matches the installed one (the two builds may not be byte-identical). @@ -500,7 +500,7 @@ The update priority chain is: 2. **Stored source** — the source the extension was originally installed from 3. **Main registry fallback** — `azd` checks the main registry for promotion opportunities -Promotion events are tracked via `ext.promote` telemetry. Upgrade events (regardless of promotion) are tracked via `ext.upgrade`. +Promotion events are tracked via `ext.promote` telemetry. Update events (regardless of promotion) are tracked via `ext.upgrade`. #### Example: Dev→Main Promotion in Action @@ -610,7 +610,7 @@ When the same extension ID is present in both `azd` and `dev`: #### Source ordering affects resolution -Sources are sorted **alphabetically by name**. With the default naming (`azd` and `dev`), `azd` is consulted first because `"azd"` sorts before `"dev"`. If you name your dev source `"aaa-dev"`, it would be consulted first. The name only affects the order in which sources are searched — it does not affect upgrade or promotion behavior. +Sources are sorted **alphabetically by name**. With the default naming (`azd` and `dev`), `azd` is consulted first because `"azd"` sorts before `"dev"`. If you name your dev source `"aaa-dev"`, it would be consulted first. The name only affects the order in which sources are searched — it does not affect update or promotion behavior. #### Stale cache after registry updates diff --git a/cli/azd/docs/tracing-in-azd.md b/cli/azd/docs/tracing-in-azd.md index 4b1b651cdd3..09a781dd937 100644 --- a/cli/azd/docs/tracing-in-azd.md +++ b/cli/azd/docs/tracing-in-azd.md @@ -82,7 +82,7 @@ adding new events for extension and hook lifecycle telemetry. | ----- | --------- | -------------------- | ---------- | | `ext.run` | Running an installed extension command through `azd`. | Command attributes such as `cmd.entry`, `cmd.flags`, `cmd.args.count`, plus `extension.installed` on the root span. | `name=ext.run`, `cmd.entry=cmd.ai.chat`, `cmd.flags=["model"]`, `cmd.args.count=0` | | `ext.install` | Installing one extension version. | `extension.id` (set as soon as installation begins); `extension.version` (set after the version is resolved). On failure the span uses OpenTelemetry status `Error`; `EndWithStatus` derives the status description from the error type. | `name=ext.install`, `extension.id=microsoft.azd.ai`, `extension.version=1.2.0`, `status=Ok` | -| `ext.upgrade` | Upgrading one extension attempt. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.upgrade.duration_ms`, `extension.upgrade.outcome`. | `name=ext.upgrade`, `extension.id=microsoft.azd.ai`, `extension.version.from=1.1.0`, `extension.version.to=1.2.0`, `extension.upgrade.outcome=upgraded` | +| `ext.upgrade` | Updating one extension attempt. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.upgrade.duration_ms`, `extension.upgrade.outcome`. | `name=ext.upgrade`, `extension.id=microsoft.azd.ai`, `extension.version.from=1.1.0`, `extension.version.to=1.2.0`, `extension.upgrade.outcome=upgraded` | | `ext.promote` | Promoting an extension registry entry, such as dev to main. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source.from`, `extension.source.to`. | `name=ext.promote`, `extension.id=microsoft.azd.ai`, `extension.source.from=dev`, `extension.source.to=main`, `status=Ok` | | `hooks.exec` | Executing a project, layer, or service lifecycle hook. | `hooks.name`, `hooks.type`, `hooks.kind`; status description uses hook-specific codes such as `hook.validation_failed`. | `name=hooks.exec`, `hooks.name=predeploy`, `hooks.type=service`, `hooks.kind=sh`, `status=Ok` | @@ -95,13 +95,13 @@ Extension telemetry attributes are defined in [`fields.go`](../internal/tracing/ | `extension.id` | Extension identifier. | `microsoft.azd.ai` | | `extension.version` | Installed extension version. | `1.2.0` | | `extension.installed` | Installed extensions on a command span, each formatted as `id@version`. | `["microsoft.azd.ai@1.2.0"]` | -| `extension.version.from` | Version before an upgrade or promotion. | `1.1.0` | -| `extension.version.to` | Version after an upgrade or promotion. | `1.2.0` | -| `extension.source` | Registry source used for an upgrade. | `main` | +| `extension.version.from` | Version before an update or promotion. | `1.1.0` | +| `extension.version.to` | Version after an update or promotion. | `1.2.0` | +| `extension.source` | Registry source used for an update. | `main` | | `extension.source.from` | Registry source before a promotion. | `dev` | | `extension.source.to` | Registry source after a promotion. | `main` | -| `extension.upgrade.duration_ms` | Upgrade duration in milliseconds. | `1532` | -| `extension.upgrade.outcome` | Upgrade result status. | `upgraded` | +| `extension.upgrade.duration_ms` | Update duration in milliseconds. | `1532` | +| `extension.upgrade.outcome` | Update result status. | `upgraded` | ### Hook Attributes @@ -207,7 +207,7 @@ These example PRs include adding both new spans and events and can be used as re ## Tool Command Telemetry -The `azd tool` command group emits telemetry that captures **per-operation outcomes** for install, upgrade, check, and show. The first-run telemetry contract remains defined for a possible future experience but is not currently emitted. All active attributes are attached as **usage attributes** via `tracing.SetUsageAttributes`, which means they appear on the user's actual command span (e.g. `cmd.tool.install`) rather than on a separate child span. +The `azd tool` command group emits telemetry that captures **per-operation outcomes** for install, update, check, and show. The first-run telemetry contract remains defined for a possible future experience but is not currently emitted. All active attributes are attached as **usage attributes** via `tracing.SetUsageAttributes`, which means they appear on the user's actual command span (e.g. `cmd.tool.install`) rather than on a separate child span. ### Dormant First-Run Experience @@ -234,17 +234,17 @@ The `tool install` / `tool update` / `tool check` / `tool show` actions emit: | Attribute | Type | Emitted by | Notes | | --- | --- | --- | --- | -| `tool.id` | string | Single-target install / upgrade / show (`len(ids) == 1`) | The built-in tool identifier. Mutually exclusive with `tool.ids`. | -| `tool.ids` | string | Multi-target batch install / upgrade (`len(ids) > 1`) | Comma-separated **sorted** built-in tool IDs. Mutually exclusive with `tool.id` — single-tool operations emit only `tool.id`. Sorting keeps attribute cardinality bounded (set vs permutation). | -| `tool.dry_run` | bool | install / upgrade | Reflects the `--dry-run` flag. | -| `tool.install.strategy` | string | Single-target install / upgrade | E.g. `winget`, `brew`, `manual`. | -| `tool.install.success` | bool | Single-target install / upgrade | Whether the per-tool operation succeeded. | -| `tool.install.success_count` | int | Batch install / upgrade | Number of tools that succeeded. | -| `tool.install.failure_count` | int | Batch install / upgrade | Number of tools that failed. | +| `tool.id` | string | Single-target install / update / show (`len(ids) == 1`) | The built-in tool identifier. Mutually exclusive with `tool.ids`. | +| `tool.ids` | string | Multi-target batch install / update (`len(ids) > 1`) | Comma-separated **sorted** built-in tool IDs. Mutually exclusive with `tool.id` — single-tool operations emit only `tool.id`. Sorting keeps attribute cardinality bounded (set vs permutation). | +| `tool.dry_run` | bool | install / update | Reflects the `--dry-run` flag. | +| `tool.install.strategy` | string | Single-target install / update | E.g. `winget`, `brew`, `manual`. | +| `tool.install.success` | bool | Single-target install / update | Whether the per-tool operation succeeded. | +| `tool.install.success_count` | int | Batch install / update | Number of tools that succeeded. | +| `tool.install.failure_count` | int | Batch install / update | Number of tools that failed. | | `tool.install.failed_ids` | string | At least one failure | Comma-separated **sorted** tool IDs whose operation failed. **Only tool IDs are recorded — error messages flow through the global error middleware (`error.message`).** When the batch call itself errors before any per-tool result is produced, the count of failed IDs may be **less than `failure_count`** (failures are synthesized from the requested set, but a synthesized entry with no `Tool` reference is omitted from the ID list to avoid emitting "unknown"). | -| `tool.install.duration_ms` | int | Batch install / upgrade | Wall-clock duration of the operation in milliseconds. Per-tool durations are intentionally **not** emitted: the cardinality cost would be `O(tools × installs)` for limited diagnostic value, and the aggregate captures the only number actionable at fleet scale. Per-tool durations remain available in the in-process `InstallResult.Duration` for local logging / dry-run reporting. | +| `tool.install.duration_ms` | int | Batch install / update | Wall-clock duration of the operation in milliseconds. Per-tool durations are intentionally **not** emitted: the cardinality cost would be `O(tools × installs)` for limited diagnostic value, and the aggregate captures the only number actionable at fleet scale. Per-tool durations remain available in the in-process `InstallResult.Duration` for local logging / dry-run reporting. | | `tool.upgrade.from_version` | string | Single-target update | Pre-update installed version. Captured via detection on both the explicit-args (`azd tool update `) and auto-detect (`azd tool update`) paths. Unset only when detection failed or the tool was not previously installed. | -| `tool.upgrade.to_version` | string | Single-target upgrade succeeded | Post-upgrade installed version. Only emitted when the upgrade succeeded — on failure `InstalledVersion` is either the unchanged pre-upgrade value or empty, which would be ambiguous against `from_version`. | +| `tool.upgrade.to_version` | string | Single-target update succeeded | Post-update installed version. Only emitted when the update succeeded — on failure `InstalledVersion` is either the unchanged pre-update value or empty, which would be ambiguous against `from_version`. | | `tool.check.updates_available` | int | `tool check` | Count of tools whose `UpdateAvailable` is `true`. | > **PII rule:** Never include free-form error strings, file paths, or user input in tool telemetry. Stick to built-in tool IDs and semver-style version strings. Error messages are already captured by the global error middleware on the same span. diff --git a/cli/azd/pkg/tools/bicep/bicep.go b/cli/azd/pkg/tools/bicep/bicep.go index 19a6197058d..fc8c90b93d5 100644 --- a/cli/azd/pkg/tools/bicep/bicep.go +++ b/cli/azd/pkg/tools/bicep/bicep.go @@ -72,7 +72,7 @@ func newCliWithTransporter( } } -// ensureInstalledOnce checks if bicep is available and downloads/upgrades if needed. +// ensureInstalledOnce checks if bicep is available and downloads or updates it if needed. // This is safe to call multiple times; successful installation is cached and failed attempts are retried. func (cli *Cli) ensureInstalledOnce(ctx context.Context) error { return cli.installInit.Do(func() error { @@ -127,7 +127,7 @@ func (cli *Cli) ensureInstalled(ctx context.Context) error { return downloadBicep(ctx, cli.transporter, Version, bicepPath) }, ); err != nil { - return fmt.Errorf("upgrading bicep: %w", err) + return fmt.Errorf("updating bicep: %w", err) } } diff --git a/docs/reference/telemetry-data.md b/docs/reference/telemetry-data.md index 44f20260737..b10e3cf02e2 100644 --- a/docs/reference/telemetry-data.md +++ b/docs/reference/telemetry-data.md @@ -72,7 +72,7 @@ Commands follow the pattern `cmd.` where spaces become dots. |-------|-------------| | `ext.run` | Extension command execution | | `ext.install` | Extension installation | -| `ext.upgrade` | Extension upgrade attempt | +| `ext.upgrade` | Extension update attempt | | `ext.promote` | Registry promotion (e.g., dev → main) | ### Agent & Copilot Events @@ -449,22 +449,22 @@ Emitted at provision start by the `microsoft.foundry` provisioning provider (the | `extension.id` | string | Extension identifier | | `extension.version` | string | Extension version | | `extension.installed` | string[] | List of installed extensions (`id@version`) | -| `extension.version.from` | string | Version before an upgrade or promotion (`ext.upgrade`, `ext.promote`) | -| `extension.version.to` | string | Version after an upgrade or promotion (`ext.upgrade`, `ext.promote`) | -| `extension.source` | string | Registry source used for an upgrade (`ext.upgrade`) | -| `extension.source.kind` | string | Kind of `--source` argument: `none`, `registered`, or `location` (`azd extension list`, `show`, `install`, `upgrade`) | +| `extension.version.from` | string | Version before an update or promotion (`ext.upgrade`, `ext.promote`) | +| `extension.version.to` | string | Version after an update or promotion (`ext.upgrade`, `ext.promote`) | +| `extension.source` | string | Registry source used for an update (`ext.upgrade`) | +| `extension.source.kind` | string | Kind of `--source` argument: `none`, `registered`, or `location` (`azd extension list`, `show`, `install`, `update`) | | `extension.source.from` | string | Registry source before a promotion (`ext.promote`) | | `extension.source.to` | string | Registry source after a promotion (`ext.promote`) | -| `extension.upgrade.duration_ms` | measurement | Duration (ms) of a single upgrade (`ext.upgrade`) | -| `extension.upgrade.outcome` | string | Upgrade result status (`ext.upgrade`) | -| `extension.dependency_of` | string | Parent extension ID when an extension is upgraded as a dependency (`ext.upgrade`) | -| `extension.dependency_upgrade_count` | measurement | Number of dependency extensions upgraded recursively (`ext.upgrade`) | +| `extension.upgrade.duration_ms` | measurement | Duration (ms) of a single update (`ext.upgrade`) | +| `extension.upgrade.outcome` | string | Update result status (`ext.upgrade`) | +| `extension.dependency_of` | string | Parent extension ID when an extension is updated as a dependency (`ext.upgrade`) | +| `extension.dependency_upgrade_count` | measurement | Number of dependency extensions updated recursively (`ext.upgrade`) |
Tool Management (azd tool) -Fields for the `azd tool` feature, including active `install`/`upgrade`/`check` operations and the reserved first-run contract for azd-managed developer tools. These are **distinct** from the [Tool Invocation Attributes](#tool-invocation-attributes-external-cli-tools) above (which describe external processes azd shells out to). +Fields for the `azd tool` feature, including active `install`/`update`/`check` operations and the reserved first-run contract for azd-managed developer tools. These are **distinct** from the [Tool Invocation Attributes](#tool-invocation-attributes-external-cli-tools) above (which describe external processes azd shells out to). > **Privacy:** only built-in tool IDs (e.g. `az-cli`, `vscode-bicep`) and version strings are captured. No file paths, no user-identifiable data, and no raw per-tool error text — failed tool IDs are recorded, but error detail stays with the global error middleware. @@ -489,7 +489,7 @@ The first-run middleware is not currently registered, so these fields are not em | `tool.firstrun.install_failed_ids` | string | Comma-separated tool IDs that failed during first-run | | `tool.firstrun.install_duration_ms` | measurement | Total first-run install duration (ms) | -**Install / upgrade / uninstall / check operations:** +**Install / update / uninstall / check operations:** | Field Key | Type | Description | |-----------|------|-------------| @@ -497,14 +497,14 @@ The first-run middleware is not currently registered, so these fields are not em | `tool.ids` | string | Comma-separated tool IDs for a batch operation | | `tool.dry_run` | string | Whether `--dry-run` was specified | | `tool.install.strategy` | string | Install strategy used. Package-manager values come from the tool manifest (`winget`, `brew`, `apt`, `npm`, `code`); the installer may also report `direct-download`, `command`, or `manual` (no available manager) | -| `tool.install.success` | string | Whether a single-target install, upgrade, or uninstall succeeded | -| `tool.install.success_count` | measurement | Tools that succeeded in a batch install/upgrade/uninstall | -| `tool.install.failure_count` | measurement | Tools that failed in a batch install/upgrade/uninstall | -| `tool.install.failed_ids` | string | Comma-separated tool IDs whose install/upgrade/uninstall failed | -| `tool.install.duration_ms` | measurement | Total install/upgrade/uninstall duration (ms) | -| `tool.upgrade.from_version` | string | Previous version (single-target upgrade) | -| `tool.upgrade.to_version` | string | New version after a successful upgrade (single-target) | -| `tool.check.updates_available` | measurement | Installed tools with an available upgrade (`azd tool check`) | +| `tool.install.success` | string | Whether a single-target install, update, or uninstall succeeded | +| `tool.install.success_count` | measurement | Tools that succeeded in a batch install/update/uninstall | +| `tool.install.failure_count` | measurement | Tools that failed in a batch install/update/uninstall | +| `tool.install.failed_ids` | string | Comma-separated tool IDs whose install/update/uninstall failed | +| `tool.install.duration_ms` | measurement | Total install/update/uninstall duration (ms) | +| `tool.upgrade.from_version` | string | Previous version (single-target update) | +| `tool.upgrade.to_version` | string | New version after a successful update (single-target) | +| `tool.check.updates_available` | measurement | Installed tools with an available update (`azd tool check`) |
From 541ee558b997179b84b7f595e6be8a26f2e4167e Mon Sep 17 00:00:00 2001 From: hyoshis Date: Mon, 3 Aug 2026 11:11:23 -0700 Subject: [PATCH 06/10] Fix update terminology test expectations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/cmd/extension_bundle_test.go | 4 ++-- cli/azd/cmd/extension_upgrade_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/azd/cmd/extension_bundle_test.go b/cli/azd/cmd/extension_bundle_test.go index 70e2b02a4a2..ed0e51ab868 100644 --- a/cli/azd/cmd/extension_bundle_test.go +++ b/cli/azd/cmd/extension_bundle_test.go @@ -188,7 +188,7 @@ func TestConfirmSourceChange(t *testing.T) { `azure.ai.agents 1.0.0 is already installed from source "azd". Reinstall from bundle?`) }) - t.Run("UpgradeShowsTargetVersion", func(t *testing.T) { + t.Run("UpdateShowsTargetVersion", func(t *testing.T) { console := mockinput.NewMockConsole() console.WhenConfirm(func(input.ConsoleOptions) bool { return true }).Respond(true) action := newConfirmTestAction(console, false) @@ -199,7 +199,7 @@ func TestConfirmSourceChange(t *testing.T) { require.NoError(t, err) require.True(t, proceed) require.Contains(t, lastConfirmMessage(console), - `azure.ai.agents 1.0.0 is already installed from source "azd". Upgrade to 2.0.0 from bundle?`) + `azure.ai.agents 1.0.0 is already installed from source "azd". Update to 2.0.0 from bundle?`) }) t.Run("DowngradeDeclined", func(t *testing.T) { diff --git a/cli/azd/cmd/extension_upgrade_test.go b/cli/azd/cmd/extension_upgrade_test.go index 1cd48880303..3e3cf7dce42 100644 --- a/cli/azd/cmd/extension_upgrade_test.go +++ b/cli/azd/cmd/extension_upgrade_test.go @@ -291,7 +291,7 @@ func TestDependencyChangeVerb(t *testing.T) { toVersion string want string }{ - {name: "upgrade", fromVersion: "1.0.0", toVersion: "2.0.0", want: "Upgraded"}, + {name: "update", fromVersion: "1.0.0", toVersion: "2.0.0", want: "Updated"}, {name: "downgrade", fromVersion: "2.0.0", toVersion: "1.0.0", want: "Downgraded"}, {name: "non-semver", fromVersion: "nightly", toVersion: "dev", want: "Updated"}, } From a853a18464f980022c29b0e8a645646d8747c0ec Mon Sep 17 00:00:00 2001 From: hyoshis Date: Mon, 3 Aug 2026 14:00:58 -0700 Subject: [PATCH 07/10] Update responsive layout terminology Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/docs/style-guidelines/responsive-layout-style-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/docs/style-guidelines/responsive-layout-style-guide.md b/cli/azd/docs/style-guidelines/responsive-layout-style-guide.md index f4c21253ffb..26a87bedbcd 100644 --- a/cli/azd/docs/style-guidelines/responsive-layout-style-guide.md +++ b/cli/azd/docs/style-guidelines/responsive-layout-style-guide.md @@ -51,7 +51,7 @@ Each column is a `PrettyColumn` (a `Column` plus responsive metadata). Use the c | Status text | Meaning | Color helper | | --- | --- | --- | | `Installed` / `Up to date` | Present and current | `WithSuccessFormat` | -| `Upgrade available` | Installed but outdated | `WithWarningFormat` | +| `Update available` | Installed but outdated | `WithWarningFormat` | | `Not installed` | Absent (not an error) | `WithGrayFormat` | ## Layout Examples From 87298c1577150a0fa7af03c2010c5a9035ae656e Mon Sep 17 00:00:00 2001 From: hyoshis Date: Mon, 3 Aug 2026 17:06:31 -0700 Subject: [PATCH 08/10] Preserve qualified legacy command hooks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/cmd/middleware/hooks.go | 19 ++++++++++-- cli/azd/cmd/middleware/hooks_test.go | 46 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/cli/azd/cmd/middleware/hooks.go b/cli/azd/cmd/middleware/hooks.go index 6c4ed532b8b..385287a1875 100644 --- a/cli/azd/cmd/middleware/hooks.go +++ b/cli/azd/cmd/middleware/hooks.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "log" + "strings" "github.com/azure/azure-dev/cli/azd/cmd/actions" "github.com/azure/azure-dev/cli/azd/pkg/environment" @@ -100,8 +101,7 @@ func (m *HooksMiddleware) registerCommandHooks( var actionResult *actions.ActionResult - commandNames := []string{m.options.CommandPath} - commandNames = append(commandNames, m.options.Aliases...) + commandNames := commandHookNames(m.options.CommandPath, m.options.Aliases) err := hooksRunner.Invoke(ctx, commandNames, "project", func() error { result, err := next(ctx) @@ -120,6 +120,21 @@ func (m *HooksMiddleware) registerCommandHooks( return actionResult, nil } +func commandHookNames(commandPath string, aliases []string) []string { + commandNames := []string{commandPath} + parentEnd := strings.LastIndex(commandPath, " ") + + for _, alias := range aliases { + // Keep leaf aliases for compatibility with existing hook behavior. + commandNames = append(commandNames, alias) + if parentEnd >= 0 { + commandNames = append(commandNames, commandPath[:parentEnd+1]+alias) + } + } + + return commandNames +} + // Registers event handlers for all services within the project configuration // Runs hooks for each matching event handler func (m *HooksMiddleware) registerServiceHooks(ctx context.Context) error { diff --git a/cli/azd/cmd/middleware/hooks_test.go b/cli/azd/cmd/middleware/hooks_test.go index 6f4177be2b3..645b6ac76ae 100644 --- a/cli/azd/cmd/middleware/hooks_test.go +++ b/cli/azd/cmd/middleware/hooks_test.go @@ -238,6 +238,52 @@ func Test_CommandHooks_Middleware_WithCmdAlias(t *testing.T) { require.True(t, *actionRan) } +func Test_CommandHooks_Middleware_WithQualifiedCmdAlias(t *testing.T) { + mockContext := mocks.NewMockContext(t.Context()) + registerHookExecutors(mockContext) + azdContext := createAzdContext(t) + + envName := "test" + runOptions := Options{CommandPath: "azd tool update", Aliases: []string{"upgrade"}} + + projectConfig := project.ProjectConfig{ + Name: envName, + Hooks: map[string][]*ext.HookConfig{ + "pretoolupgrade": { + { + Run: "echo 'pretoolupgrade hook'", + Shell: string(language.HookKindBash), + }, + }, + "posttoolupgrade": { + { + Run: "echo 'posttoolupgrade hook'", + Shell: string(language.HookKindBash), + }, + }, + }, + } + + err := ensureAzdValid(mockContext, azdContext, envName, &projectConfig) + require.NoError(t, err) + + hookCount := 0 + mockContext.CommandRunner.When(func(args exec.RunArgs, command string) bool { + return true + }).RespondFn(func(args exec.RunArgs) (exec.RunResult, error) { + hookCount++ + return exec.NewRunResult(0, "", ""), nil + }) + + nextFn, actionRan := createNextFn() + result, err := runMiddleware(mockContext, envName, &projectConfig, &runOptions, nextFn) + + require.NotNil(t, result) + require.NoError(t, err) + require.True(t, *actionRan) + require.Equal(t, 2, hookCount) +} + func Test_ServiceHooks_Registered(t *testing.T) { mockContext := mocks.NewMockContext(t.Context()) registerHookExecutors(mockContext) From 8c10781a65181772b420d8d3987d2e9c1d3c522b Mon Sep 17 00:00:00 2001 From: hyoshis Date: Tue, 4 Aug 2026 11:56:35 -0700 Subject: [PATCH 09/10] Revert "Preserve qualified legacy command hooks" This reverts commit 87298c1577150a0fa7af03c2010c5a9035ae656e. --- cli/azd/cmd/middleware/hooks.go | 19 ++---------- cli/azd/cmd/middleware/hooks_test.go | 46 ---------------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/cli/azd/cmd/middleware/hooks.go b/cli/azd/cmd/middleware/hooks.go index 385287a1875..6c4ed532b8b 100644 --- a/cli/azd/cmd/middleware/hooks.go +++ b/cli/azd/cmd/middleware/hooks.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "log" - "strings" "github.com/azure/azure-dev/cli/azd/cmd/actions" "github.com/azure/azure-dev/cli/azd/pkg/environment" @@ -101,7 +100,8 @@ func (m *HooksMiddleware) registerCommandHooks( var actionResult *actions.ActionResult - commandNames := commandHookNames(m.options.CommandPath, m.options.Aliases) + commandNames := []string{m.options.CommandPath} + commandNames = append(commandNames, m.options.Aliases...) err := hooksRunner.Invoke(ctx, commandNames, "project", func() error { result, err := next(ctx) @@ -120,21 +120,6 @@ func (m *HooksMiddleware) registerCommandHooks( return actionResult, nil } -func commandHookNames(commandPath string, aliases []string) []string { - commandNames := []string{commandPath} - parentEnd := strings.LastIndex(commandPath, " ") - - for _, alias := range aliases { - // Keep leaf aliases for compatibility with existing hook behavior. - commandNames = append(commandNames, alias) - if parentEnd >= 0 { - commandNames = append(commandNames, commandPath[:parentEnd+1]+alias) - } - } - - return commandNames -} - // Registers event handlers for all services within the project configuration // Runs hooks for each matching event handler func (m *HooksMiddleware) registerServiceHooks(ctx context.Context) error { diff --git a/cli/azd/cmd/middleware/hooks_test.go b/cli/azd/cmd/middleware/hooks_test.go index 645b6ac76ae..6f4177be2b3 100644 --- a/cli/azd/cmd/middleware/hooks_test.go +++ b/cli/azd/cmd/middleware/hooks_test.go @@ -238,52 +238,6 @@ func Test_CommandHooks_Middleware_WithCmdAlias(t *testing.T) { require.True(t, *actionRan) } -func Test_CommandHooks_Middleware_WithQualifiedCmdAlias(t *testing.T) { - mockContext := mocks.NewMockContext(t.Context()) - registerHookExecutors(mockContext) - azdContext := createAzdContext(t) - - envName := "test" - runOptions := Options{CommandPath: "azd tool update", Aliases: []string{"upgrade"}} - - projectConfig := project.ProjectConfig{ - Name: envName, - Hooks: map[string][]*ext.HookConfig{ - "pretoolupgrade": { - { - Run: "echo 'pretoolupgrade hook'", - Shell: string(language.HookKindBash), - }, - }, - "posttoolupgrade": { - { - Run: "echo 'posttoolupgrade hook'", - Shell: string(language.HookKindBash), - }, - }, - }, - } - - err := ensureAzdValid(mockContext, azdContext, envName, &projectConfig) - require.NoError(t, err) - - hookCount := 0 - mockContext.CommandRunner.When(func(args exec.RunArgs, command string) bool { - return true - }).RespondFn(func(args exec.RunArgs) (exec.RunResult, error) { - hookCount++ - return exec.NewRunResult(0, "", ""), nil - }) - - nextFn, actionRan := createNextFn() - result, err := runMiddleware(mockContext, envName, &projectConfig, &runOptions, nextFn) - - require.NotNil(t, result) - require.NoError(t, err) - require.True(t, *actionRan) - require.Equal(t, 2, hookCount) -} - func Test_ServiceHooks_Registered(t *testing.T) { mockContext := mocks.NewMockContext(t.Context()) registerHookExecutors(mockContext) From cdaa45288a19e474ff53cd87585f51b41f7e9e8f Mon Sep 17 00:00:00 2001 From: hyoshis Date: Tue, 4 Aug 2026 12:38:50 -0700 Subject: [PATCH 10/10] Align update JSON and telemetry names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21cd8119-a644-4b3c-8c99-3f89f63a9968 --- cli/azd/cmd/extension.go | 12 ++-- cli/azd/cmd/telemetry_test.go | 27 +++++++-- cli/azd/cmd/tool.go | 24 ++------ cli/azd/cmd/tool_test.go | 57 ++++--------------- .../extension-resolution-and-versioning.md | 2 +- cli/azd/docs/tracing-in-azd.md | 10 ++-- cli/azd/internal/tracing/events/events.go | 4 +- cli/azd/internal/tracing/fields/fields.go | 42 +++++++------- cli/azd/pkg/extensions/manager.go | 4 +- docs/architecture/telemetry.md | 2 +- docs/reference/telemetry-data.md | 22 +++---- .../metrics-audit/feature-telemetry-matrix.md | 10 ++-- docs/specs/metrics-audit/telemetry-schema.md | 12 ++-- 13 files changed, 98 insertions(+), 130 deletions(-) diff --git a/cli/azd/cmd/extension.go b/cli/azd/cmd/extension.go index b8b65be4173..791ddf9c1c2 100644 --- a/cli/azd/cmd/extension.go +++ b/cli/azd/cmd/extension.go @@ -2104,8 +2104,8 @@ func (a *extensionUpgradeAction) upgradeOneExtension( startTime := time.Now() baseResult := extensions.UpgradeResult{ExtensionId: extensionId} - // Start a telemetry span for this individual extension upgrade. - ctx, span := tracing.Start(ctx, events.ExtensionUpgradeEvent) + // Start a telemetry span for this individual extension update. + ctx, span := tracing.Start(ctx, events.ExtensionUpdateEvent) defer func() { elapsed := time.Since(startTime).Milliseconds() span.SetAttributes( @@ -2119,16 +2119,16 @@ func (a *extensionUpgradeAction) upgradeOneExtension( fields.ExtensionSource.String( baseResult.ToSource, ), - fields.ExtensionUpgradeDurationMs.Int64(elapsed), - fields.ExtensionUpgradeOutcome.String( + fields.ExtensionUpdateDurationMs.Int64(elapsed), + fields.ExtensionUpdateOutcome.String( baseResult.Status.String(), ), - fields.ExtensionDependencyUpgradeCount.Int( + fields.ExtensionDependencyUpdateCount.Int( extensions.CountDependencyUpgrades(baseResult.DependencyUpgrades), ), ) if baseResult.Status == extensions.UpgradeStatusFailed { - span.SetStatus(codes.Error, "upgrade.failed") + span.SetStatus(codes.Error, "update.failed") } else { span.SetStatus(codes.Ok, "") } diff --git a/cli/azd/cmd/telemetry_test.go b/cli/azd/cmd/telemetry_test.go index 6be43023bcb..dff134672dc 100644 --- a/cli/azd/cmd/telemetry_test.go +++ b/cli/azd/cmd/telemetry_test.go @@ -9,9 +9,15 @@ import ( "github.com/stretchr/testify/require" "github.com/azure/azure-dev/cli/azd/internal" + "github.com/azure/azure-dev/cli/azd/internal/tracing/events" "github.com/azure/azure-dev/cli/azd/internal/tracing/fields" ) +func TestTelemetryEventConstants(t *testing.T) { + t.Parallel() + require.Equal(t, "ext.update", events.ExtensionUpdateEvent) +} + // TestTelemetryFieldConstants verifies that all telemetry field constants added for // command-specific instrumentation are properly defined and produce valid attribute // key-value pairs. This is a contract test: if a field constant is removed or renamed, @@ -158,11 +164,11 @@ func TestTelemetryFieldConstants(t *testing.T) { kvFRInstallDuration := fields.ToolFirstRunInstallDurationMsKey.Int64(1234) require.Equal(t, "tool.firstrun.install_duration_ms", string(kvFRInstallDuration.Key)) - kvFromVer := fields.ToolUpgradeFromVersionKey.String("1.0.0") - require.Equal(t, "tool.upgrade.from_version", string(kvFromVer.Key)) + kvFromVer := fields.ToolUpdateFromVersionKey.String("1.0.0") + require.Equal(t, "tool.update.from_version", string(kvFromVer.Key)) - kvToVer := fields.ToolUpgradeToVersionKey.String("1.1.0") - require.Equal(t, "tool.upgrade.to_version", string(kvToVer.Key)) + kvToVer := fields.ToolUpdateToVersionKey.String("1.1.0") + require.Equal(t, "tool.update.to_version", string(kvToVer.Key)) kvUpdates := fields.ToolCheckUpdatesAvailableKey.Int(3) require.Equal(t, "tool.check.updates_available", string(kvUpdates.Key)) @@ -173,6 +179,15 @@ func TestTelemetryFieldConstants(t *testing.T) { kv := fields.ExtensionSourceKind.String("location") require.Equal(t, "extension.source.kind", string(kv.Key)) require.Equal(t, "location", kv.Value.AsString()) + + duration := fields.ExtensionUpdateDurationMs.Int64(1234) + require.Equal(t, "extension.update.duration_ms", string(duration.Key)) + + outcome := fields.ExtensionUpdateOutcome.String("upgraded") + require.Equal(t, "extension.update.outcome", string(outcome.Key)) + + dependencyCount := fields.ExtensionDependencyUpdateCount.Int(2) + require.Equal(t, "extension.dependency_update_count", string(dependencyCount.Key)) }) // Provision validation telemetry fields (emitted by both the Bicep @@ -238,7 +253,7 @@ func TestCommandTelemetryCoverage(t *testing.T) { "extension install", // extension.source.kind "extension list", // extension.source.kind "extension show", // extension.source.kind - "extension update", // extension.source.kind + extension upgrade spans + "extension update", // extension.source.kind + extension update spans "hooks run", // hooks.name, hooks.type "infra generate", // infra.provider "init", // init.method, appinit.* fields @@ -250,7 +265,7 @@ func TestCommandTelemetryCoverage(t *testing.T) { "tool install", // tool.id(s), tool.dry_run, tool.install.* aggregate + per-tool fields "tool show", // tool.id "tool uninstall", // tool.id(s), tool.dry_run, tool.install.* aggregate + per-tool fields - "tool update", // tool.id(s), tool.dry_run, tool.install.* aggregate + tool.upgrade.* versions + "tool update", // tool.id(s), tool.dry_run, tool.install.* aggregate + tool.update.* versions "up", // infra.provider (via provisioning manager; composes provision+deploy) "update", // update.* fields } diff --git a/cli/azd/cmd/tool.go b/cli/azd/cmd/tool.go index cd05f806fad..ebf3a4c7d30 100644 --- a/cli/azd/cmd/tool.go +++ b/cli/azd/cmd/tool.go @@ -29,8 +29,8 @@ import ( // singleResultCommonAttrs returns the usage attributes shared by single-target // `azd tool install` and `azd tool update`: success, tool.id, and the -// installation strategy. Callers append upgrade-specific version attrs -// (tool.upgrade.{from,to}_version) on top. +// installation strategy. Callers append update-specific version attrs +// (tool.update.{from,to}_version) on top. // // Returns nil if r is nil so callers can safely pass through results without // pre-validating the slice element. @@ -53,7 +53,7 @@ func singleResultCommonAttrs(r *tool.InstallResult) []attribute.KeyValue { // emitToolInstallTelemetry emits aggregate telemetry attributes for a batch // install or upgrade operation. When the batch contains exactly one tool the // caller is responsible for also emitting tool.id, tool.install.strategy, and -// tool.install.success (and, for upgrades, tool.upgrade.{from,to}_version). +// tool.install.success (and, for updates, tool.update.{from,to}_version). // // When the batch infrastructure itself fails (opErr != nil and results is // empty) every requested tool is counted as a failure and its ID is added to @@ -1223,7 +1223,7 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err var toolsToUpgrade []*tool.ToolDefinition // fromVersions captures the pre-upgrade installed version per tool ID, - // populated on both branches so that tool.upgrade.from_version is + // populated on both branches so that tool.update.from_version is // emitted on the single-tool path regardless of whether the user // supplied explicit args. Detection failures are non-fatal here — // from_version is a best-effort telemetry signal, not a precondition @@ -1372,21 +1372,16 @@ func (a *toolUpgradeAction) Run(ctx context.Context) (*actions.ActionResult, err singleAttrs := singleResultCommonAttrs(r) if r.Tool != nil { if from, ok := fromVersions[r.Tool.Id]; ok && from != "" { - singleAttrs = append(singleAttrs, fields.ToolUpgradeFromVersionKey.String(from)) + singleAttrs = append(singleAttrs, fields.ToolUpdateFromVersionKey.String(from)) } } if r.Success && r.InstalledVersion != "" { - singleAttrs = append(singleAttrs, fields.ToolUpgradeToVersionKey.String(r.InstalledVersion)) + singleAttrs = append(singleAttrs, fields.ToolUpdateToVersionKey.String(r.InstalledVersion)) } tracing.SetUsageAttributes(singleAttrs...) } if a.formatter.Kind() == output.JsonFormat { - // Keep the existing machine-readable action value stable for callers - // using either the update command or its upgrade alias. - for _, result := range upgradeResults { - result.Action = "upgrade" - } return nil, a.formatter.Format(upgradeResults, a.writer, nil) } @@ -1556,13 +1551,6 @@ func (a *toolUpgradeAction) dryRun( } if a.formatter.Kind() == output.JsonFormat { - // Keep the existing machine-readable action value stable for callers - // using either the update command or its upgrade alias. - for i := range rows { - if rows[i].Action == "update" { - rows[i].Action = "upgrade" - } - } return nil, a.formatter.Format(rows, a.writer, nil) } diff --git a/cli/azd/cmd/tool_test.go b/cli/azd/cmd/tool_test.go index 5e44b734a40..e49ae2b471d 100644 --- a/cli/azd/cmd/tool_test.go +++ b/cli/azd/cmd/tool_test.go @@ -618,40 +618,6 @@ func TestToolInstallAction_DryRun_MultiTool_EmitsSortedToolIds(t *testing.T) { assert.False(t, ok, "tool.id must NOT be emitted alongside tool.ids (mutual exclusion)") } -func TestToolUpgradeAction_DryRun_JsonPreservesSkippedAction(t *testing.T) { - tracing.ResetUsageAttributesForTest() - - detector := &cmdMockDetector{ - detectTool: func(_ context.Context, td *tool.ToolDefinition) (*tool.ToolStatus, error) { - return &tool.ToolStatus{ - Tool: td, - Installed: td.Id == "az-cli", - InstalledVersion: "1.0.0", - }, nil - }, - } - manager := tool.NewManager(detector, &cmdMockInstaller{}, nil) - - var buf bytes.Buffer - action := newToolUpgradeAction( - []string{"az-cli", "github-copilot-cli"}, - &toolUpgradeFlags{dryRun: true}, - manager, - mockinput.NewMockConsole(), - &output.JsonFormatter{}, - &buf, - ) - - _, err := action.Run(t.Context()) - require.NoError(t, err) - - var items []toolDryRunItem - require.NoError(t, json.Unmarshal(buf.Bytes(), &items)) - require.Len(t, items, 2) - assert.Equal(t, "upgrade", items[0].Action) - assert.Equal(t, "skip (not installed)", items[1].Action) -} - // TestToolInstallAction_AllFailureBatch_EmitsCorrectAggregates exercises the // install path end-to-end against a mock Installer that fails every per-tool // install. It verifies the aggregate telemetry contract: @@ -784,9 +750,9 @@ func TestToolInstallAction_Failure_ReturnsErrorNotSuccess(t *testing.T) { // TestToolUpgradeAction_SuccessEmitsFromAndToVersion exercises the upgrade // path end-to-end and verifies: -// - tool.upgrade.from_version is emitted from DetectTool (H2: no UX change, +// - tool.update.from_version is emitted from DetectTool (H2: no UX change, // reuses the existing detector) -// - tool.upgrade.to_version is emitted only on Success and reflects the +// - tool.update.to_version is emitted only on Success and reflects the // installer's InstalledVersion (H3) // - tool.id is emitted (single-tool, not tool.ids) func TestToolUpgradeAction_SuccessEmitsFromAndToVersion(t *testing.T) { @@ -829,17 +795,17 @@ func TestToolUpgradeAction_SuccessEmitsFromAndToVersion(t *testing.T) { require.True(t, ok) assert.Equal(t, "az-cli", gotID) - gotFrom, ok := lookupToolStrUsage(string(fields.ToolUpgradeFromVersionKey.Key)) - require.True(t, ok, "tool.upgrade.from_version must be emitted from detector output") + gotFrom, ok := lookupToolStrUsage(string(fields.ToolUpdateFromVersionKey.Key)) + require.True(t, ok, "tool.update.from_version must be emitted from detector output") assert.Equal(t, "1.0.0", gotFrom) - gotTo, ok := lookupToolStrUsage(string(fields.ToolUpgradeToVersionKey.Key)) - require.True(t, ok, "tool.upgrade.to_version must be emitted on success") + gotTo, ok := lookupToolStrUsage(string(fields.ToolUpdateToVersionKey.Key)) + require.True(t, ok, "tool.update.to_version must be emitted on success") assert.Equal(t, "2.0.0", gotTo) } // TestToolUpgradeAction_FailureDoesNotEmitToVersion verifies the H3 contract: -// when the upgrade fails, tool.upgrade.to_version is NOT emitted (since there +// when the update fails, tool.update.to_version is NOT emitted (since there // is no successfully-installed version to report). func TestToolUpgradeAction_FailureDoesNotEmitToVersion(t *testing.T) { tracing.ResetUsageAttributesForTest() @@ -876,13 +842,13 @@ func TestToolUpgradeAction_FailureDoesNotEmitToVersion(t *testing.T) { _, _ = action.Run(t.Context()) // from_version still emits (detected before the failed upgrade). - gotFrom, ok := lookupToolStrUsage(string(fields.ToolUpgradeFromVersionKey.Key)) + gotFrom, ok := lookupToolStrUsage(string(fields.ToolUpdateFromVersionKey.Key)) require.True(t, ok) assert.Equal(t, "1.0.0", gotFrom) // to_version must be absent — there is no installed version to report. - _, ok = lookupToolStrUsage(string(fields.ToolUpgradeToVersionKey.Key)) - assert.False(t, ok, "tool.upgrade.to_version must not be emitted on upgrade failure") + _, ok = lookupToolStrUsage(string(fields.ToolUpdateToVersionKey.Key)) + assert.False(t, ok, "tool.update.to_version must not be emitted on update failure") } // --------------------------------------------------------------------------- @@ -1476,8 +1442,7 @@ func TestToolUpgradeAction_All_JsonFormat_EmitsCleanJson(t *testing.T) { require.NoError(t, json.Unmarshal(buf.Bytes(), &items), "update --all --output json must emit valid JSON") require.NotEmpty(t, items, "at least one installed tool must be reported") - require.Equal(t, "upgrade", items[0].Action, - "the machine-readable action must remain stable for backward compatibility") + require.Equal(t, "update", items[0].Action) } // TestToolUpgradeAction_All_JsonFormat_EmptyEmitsArray verifies that when there diff --git a/cli/azd/docs/extensions/extension-resolution-and-versioning.md b/cli/azd/docs/extensions/extension-resolution-and-versioning.md index c0b2c53159d..6908611bffc 100644 --- a/cli/azd/docs/extensions/extension-resolution-and-versioning.md +++ b/cli/azd/docs/extensions/extension-resolution-and-versioning.md @@ -500,7 +500,7 @@ The update priority chain is: 2. **Stored source** — the source the extension was originally installed from 3. **Main registry fallback** — `azd` checks the main registry for promotion opportunities -Promotion events are tracked via `ext.promote` telemetry. Update events (regardless of promotion) are tracked via `ext.upgrade`. +Promotion events are tracked via `ext.promote` telemetry. Update events (regardless of promotion) are tracked via `ext.update`. #### Example: Dev→Main Promotion in Action diff --git a/cli/azd/docs/tracing-in-azd.md b/cli/azd/docs/tracing-in-azd.md index 09a781dd937..33c895630c3 100644 --- a/cli/azd/docs/tracing-in-azd.md +++ b/cli/azd/docs/tracing-in-azd.md @@ -82,7 +82,7 @@ adding new events for extension and hook lifecycle telemetry. | ----- | --------- | -------------------- | ---------- | | `ext.run` | Running an installed extension command through `azd`. | Command attributes such as `cmd.entry`, `cmd.flags`, `cmd.args.count`, plus `extension.installed` on the root span. | `name=ext.run`, `cmd.entry=cmd.ai.chat`, `cmd.flags=["model"]`, `cmd.args.count=0` | | `ext.install` | Installing one extension version. | `extension.id` (set as soon as installation begins); `extension.version` (set after the version is resolved). On failure the span uses OpenTelemetry status `Error`; `EndWithStatus` derives the status description from the error type. | `name=ext.install`, `extension.id=microsoft.azd.ai`, `extension.version=1.2.0`, `status=Ok` | -| `ext.upgrade` | Updating one extension attempt. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.upgrade.duration_ms`, `extension.upgrade.outcome`. | `name=ext.upgrade`, `extension.id=microsoft.azd.ai`, `extension.version.from=1.1.0`, `extension.version.to=1.2.0`, `extension.upgrade.outcome=upgraded` | +| `ext.update` | Updating one extension attempt. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.update.duration_ms`, `extension.update.outcome`. | `name=ext.update`, `extension.id=microsoft.azd.ai`, `extension.version.from=1.1.0`, `extension.version.to=1.2.0`, `extension.update.outcome=upgraded` | | `ext.promote` | Promoting an extension registry entry, such as dev to main. | `extension.id`, `extension.version.from`, `extension.version.to`, `extension.source.from`, `extension.source.to`. | `name=ext.promote`, `extension.id=microsoft.azd.ai`, `extension.source.from=dev`, `extension.source.to=main`, `status=Ok` | | `hooks.exec` | Executing a project, layer, or service lifecycle hook. | `hooks.name`, `hooks.type`, `hooks.kind`; status description uses hook-specific codes such as `hook.validation_failed`. | `name=hooks.exec`, `hooks.name=predeploy`, `hooks.type=service`, `hooks.kind=sh`, `status=Ok` | @@ -100,8 +100,8 @@ Extension telemetry attributes are defined in [`fields.go`](../internal/tracing/ | `extension.source` | Registry source used for an update. | `main` | | `extension.source.from` | Registry source before a promotion. | `dev` | | `extension.source.to` | Registry source after a promotion. | `main` | -| `extension.upgrade.duration_ms` | Update duration in milliseconds. | `1532` | -| `extension.upgrade.outcome` | Update result status. | `upgraded` | +| `extension.update.duration_ms` | Update duration in milliseconds. | `1532` | +| `extension.update.outcome` | Update result status. | `upgraded` | ### Hook Attributes @@ -243,8 +243,8 @@ The `tool install` / `tool update` / `tool check` / `tool show` actions emit: | `tool.install.failure_count` | int | Batch install / update | Number of tools that failed. | | `tool.install.failed_ids` | string | At least one failure | Comma-separated **sorted** tool IDs whose operation failed. **Only tool IDs are recorded — error messages flow through the global error middleware (`error.message`).** When the batch call itself errors before any per-tool result is produced, the count of failed IDs may be **less than `failure_count`** (failures are synthesized from the requested set, but a synthesized entry with no `Tool` reference is omitted from the ID list to avoid emitting "unknown"). | | `tool.install.duration_ms` | int | Batch install / update | Wall-clock duration of the operation in milliseconds. Per-tool durations are intentionally **not** emitted: the cardinality cost would be `O(tools × installs)` for limited diagnostic value, and the aggregate captures the only number actionable at fleet scale. Per-tool durations remain available in the in-process `InstallResult.Duration` for local logging / dry-run reporting. | -| `tool.upgrade.from_version` | string | Single-target update | Pre-update installed version. Captured via detection on both the explicit-args (`azd tool update `) and auto-detect (`azd tool update`) paths. Unset only when detection failed or the tool was not previously installed. | -| `tool.upgrade.to_version` | string | Single-target update succeeded | Post-update installed version. Only emitted when the update succeeded — on failure `InstalledVersion` is either the unchanged pre-update value or empty, which would be ambiguous against `from_version`. | +| `tool.update.from_version` | string | Single-target update | Pre-update installed version. Captured via detection on both the explicit-args (`azd tool update `) and auto-detect (`azd tool update`) paths. Unset only when detection failed or the tool was not previously installed. | +| `tool.update.to_version` | string | Single-target update succeeded | Post-update installed version. Only emitted when the update succeeded — on failure `InstalledVersion` is either the unchanged pre-update value or empty, which would be ambiguous against `from_version`. | | `tool.check.updates_available` | int | `tool check` | Count of tools whose `UpdateAvailable` is `true`. | > **PII rule:** Never include free-form error strings, file paths, or user input in tool telemetry. Stick to built-in tool IDs and semver-style version strings. Error messages are already captured by the global error middleware on the same span. diff --git a/cli/azd/internal/tracing/events/events.go b/cli/azd/internal/tracing/events/events.go index 5e89fb7cbf7..fa279a0cb30 100644 --- a/cli/azd/internal/tracing/events/events.go +++ b/cli/azd/internal/tracing/events/events.go @@ -28,8 +28,8 @@ const AgentTroubleshootEvent = "agent.troubleshoot" const ( ExtensionRunEvent = "ext.run" ExtensionInstallEvent = "ext.install" - // ExtensionUpgradeEvent tracks a single extension upgrade attempt. - ExtensionUpgradeEvent = "ext.upgrade" + // ExtensionUpdateEvent tracks a single extension update attempt. + ExtensionUpdateEvent = "ext.update" // ExtensionPromoteEvent tracks a registry promotion (e.g., dev → main). ExtensionPromoteEvent = "ext.promote" ) diff --git a/cli/azd/internal/tracing/fields/fields.go b/cli/azd/internal/tracing/fields/fields.go index 4493d266f63..ff0064aa05b 100644 --- a/cli/azd/internal/tracing/fields/fields.go +++ b/cli/azd/internal/tracing/fields/fields.go @@ -626,19 +626,19 @@ var ( IsMeasurement: true, } - // ToolUpgradeFromVersionKey records the previous version of a tool - // being upgraded (single-target upgrades only). - ToolUpgradeFromVersionKey = AttributeKey{ - Key: attribute.Key("tool.upgrade.from_version"), + // ToolUpdateFromVersionKey records the previous version of a tool + // being updated (single-target updates only). + ToolUpdateFromVersionKey = AttributeKey{ + Key: attribute.Key("tool.update.from_version"), Classification: SystemMetadata, Purpose: FeatureInsight, } - // ToolUpgradeToVersionKey records the new version after upgrade - // (single-target upgrades only). Emitted only when the upgrade + // ToolUpdateToVersionKey records the new version after update + // (single-target updates only). Emitted only when the update // succeeds. - ToolUpgradeToVersionKey = AttributeKey{ - Key: attribute.Key("tool.upgrade.to_version"), + ToolUpdateToVersionKey = AttributeKey{ + Key: attribute.Key("tool.update.to_version"), Classification: SystemMetadata, Purpose: FeatureInsight, } @@ -1160,19 +1160,19 @@ var ( Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionVersionFrom is the installed version before an upgrade. + // ExtensionVersionFrom is the installed version before an update. ExtensionVersionFrom = AttributeKey{ Key: attribute.Key("extension.version.from"), Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionVersionTo is the target version after an upgrade. + // ExtensionVersionTo is the target version after an update. ExtensionVersionTo = AttributeKey{ Key: attribute.Key("extension.version.to"), Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionSource is the registry source used for the upgrade. + // ExtensionSource is the registry source used for the update. ExtensionSource = AttributeKey{ Key: attribute.Key("extension.source"), Classification: SystemMetadata, @@ -1196,28 +1196,28 @@ var ( Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionUpgradeDurationMs is the time in milliseconds for one upgrade. - ExtensionUpgradeDurationMs = AttributeKey{ - Key: attribute.Key("extension.upgrade.duration_ms"), + // ExtensionUpdateDurationMs is the time in milliseconds for one update. + ExtensionUpdateDurationMs = AttributeKey{ + Key: attribute.Key("extension.update.duration_ms"), Classification: SystemMetadata, Purpose: PerformanceAndHealth, IsMeasurement: true, } - // ExtensionUpgradeOutcome is the upgrade result status. - ExtensionUpgradeOutcome = AttributeKey{ - Key: attribute.Key("extension.upgrade.outcome"), + // ExtensionUpdateOutcome is the update result status. + ExtensionUpdateOutcome = AttributeKey{ + Key: attribute.Key("extension.update.outcome"), Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionDependencyOf is the parent extension for a dependency upgrade. + // ExtensionDependencyOf is the parent extension for a dependency update. ExtensionDependencyOf = AttributeKey{ Key: attribute.Key("extension.dependency_of"), Classification: SystemMetadata, Purpose: FeatureInsight, } - // ExtensionDependencyUpgradeCount is the recursive dependency upgrade count. - ExtensionDependencyUpgradeCount = AttributeKey{ - Key: attribute.Key("extension.dependency_upgrade_count"), + // ExtensionDependencyUpdateCount is the recursive dependency update count. + ExtensionDependencyUpdateCount = AttributeKey{ + Key: attribute.Key("extension.dependency_update_count"), Classification: SystemMetadata, Purpose: FeatureInsight, IsMeasurement: true, diff --git a/cli/azd/pkg/extensions/manager.go b/cli/azd/pkg/extensions/manager.go index eae56416dda..1798d8d9ad2 100644 --- a/cli/azd/pkg/extensions/manager.go +++ b/cli/azd/pkg/extensions/manager.go @@ -1169,8 +1169,8 @@ func (m *Manager) evaluateDependencyChanges( FromSource: installed.Source, } - // Correlate the child upgrade with its triggering parent. - childCtx, span := tracing.Start(ctx, events.ExtensionUpgradeEvent) + // Correlate the child update with its triggering parent. + childCtx, span := tracing.Start(ctx, events.ExtensionUpdateEvent) span.SetAttributes( fields.ExtensionId.String(dep.Id), fields.ExtensionDependencyOf.String(parentExtension.Id), diff --git a/docs/architecture/telemetry.md b/docs/architecture/telemetry.md index bee6971d45f..61ae7a75e14 100644 --- a/docs/architecture/telemetry.md +++ b/docs/architecture/telemetry.md @@ -182,7 +182,7 @@ flowchart LR - Validation: `ext.validation.*` - Auth: `ext.auth.*` - Dependency: `ext.dependency.*` -- Extension lifecycle events: `ext.install`, `ext.upgrade`, `ext.promote` +- Extension lifecycle events: `ext.install`, `ext.update`, `ext.promote` ## Consent & Privacy diff --git a/docs/reference/telemetry-data.md b/docs/reference/telemetry-data.md index b10e3cf02e2..1a948ad3017 100644 --- a/docs/reference/telemetry-data.md +++ b/docs/reference/telemetry-data.md @@ -72,7 +72,7 @@ Commands follow the pattern `cmd.` where spaces become dots. |-------|-------------| | `ext.run` | Extension command execution | | `ext.install` | Extension installation | -| `ext.upgrade` | Extension update attempt | +| `ext.update` | Extension update attempt | | `ext.promote` | Registry promotion (e.g., dev → main) | ### Agent & Copilot Events @@ -449,16 +449,16 @@ Emitted at provision start by the `microsoft.foundry` provisioning provider (the | `extension.id` | string | Extension identifier | | `extension.version` | string | Extension version | | `extension.installed` | string[] | List of installed extensions (`id@version`) | -| `extension.version.from` | string | Version before an update or promotion (`ext.upgrade`, `ext.promote`) | -| `extension.version.to` | string | Version after an update or promotion (`ext.upgrade`, `ext.promote`) | -| `extension.source` | string | Registry source used for an update (`ext.upgrade`) | +| `extension.version.from` | string | Version before an update or promotion (`ext.update`, `ext.promote`) | +| `extension.version.to` | string | Version after an update or promotion (`ext.update`, `ext.promote`) | +| `extension.source` | string | Registry source used for an update (`ext.update`) | | `extension.source.kind` | string | Kind of `--source` argument: `none`, `registered`, or `location` (`azd extension list`, `show`, `install`, `update`) | | `extension.source.from` | string | Registry source before a promotion (`ext.promote`) | | `extension.source.to` | string | Registry source after a promotion (`ext.promote`) | -| `extension.upgrade.duration_ms` | measurement | Duration (ms) of a single update (`ext.upgrade`) | -| `extension.upgrade.outcome` | string | Update result status (`ext.upgrade`) | -| `extension.dependency_of` | string | Parent extension ID when an extension is updated as a dependency (`ext.upgrade`) | -| `extension.dependency_upgrade_count` | measurement | Number of dependency extensions updated recursively (`ext.upgrade`) | +| `extension.update.duration_ms` | measurement | Duration (ms) of a single update (`ext.update`) | +| `extension.update.outcome` | string | Update result status (`ext.update`) | +| `extension.dependency_of` | string | Parent extension ID when an extension is updated as a dependency (`ext.update`) | +| `extension.dependency_update_count` | measurement | Number of dependency extensions updated recursively (`ext.update`) |
@@ -502,8 +502,8 @@ The first-run middleware is not currently registered, so these fields are not em | `tool.install.failure_count` | measurement | Tools that failed in a batch install/update/uninstall | | `tool.install.failed_ids` | string | Comma-separated tool IDs whose install/update/uninstall failed | | `tool.install.duration_ms` | measurement | Total install/update/uninstall duration (ms) | -| `tool.upgrade.from_version` | string | Previous version (single-target update) | -| `tool.upgrade.to_version` | string | New version after a successful update (single-target) | +| `tool.update.from_version` | string | Previous version (single-target update) | +| `tool.update.to_version` | string | New version after a successful update (single-target) | | `tool.check.updates_available` | measurement | Installed tools with an available update (`azd tool check`) |
@@ -698,7 +698,7 @@ How to find telemetry for a given feature area. Start here if you know the featu | **Provisioning (IaC)** | `cmd.provision`, `cmd.up`, `cmd.down`, `arm.deploy.*`, `arm.validate.*` | `infra.provider` (`bicep`/`terraform`/`arm`/`pulumi`/custom; slice of each distinct provider for multi-layer projects) | Provision success, ARM errors, duration | | **Authentication** | `cmd.auth.login` | `auth.method` | Auth method usage, failure rates | | **CI/CD Pipelines** | `cmd.pipeline.config` | `pipeline.provider` | Pipeline setup adoption | -| **Extensions** | `ext.run`, `ext.install`, `ext.upgrade` | `extension.id`, `extension.version`, `extension.installed` | Extension adoption, errors | +| **Extensions** | `ext.run`, `ext.install`, `ext.update` | `extension.id`, `extension.version`, `extension.installed` | Extension adoption, errors | | **MCP** | `mcp.` | `mcp.client.name`, `mcp.client.version` | Tool usage by client | | **Agentic (Copilot)** | `copilot.initialize`, `copilot.session` | `copilot.mode`, `copilot.init.model`, `copilot.message.*` | Session counts, token usage | | **Agent Troubleshooting** | `agent.troubleshoot` | `agent.fix.attempts` | Auto-fix adoption, retry counts | diff --git a/docs/specs/metrics-audit/feature-telemetry-matrix.md b/docs/specs/metrics-audit/feature-telemetry-matrix.md index 8a11e5e73cb..10cb1ab82b4 100644 --- a/docs/specs/metrics-audit/feature-telemetry-matrix.md +++ b/docs/specs/metrics-audit/feature-telemetry-matrix.md @@ -30,9 +30,9 @@ These commands emit attributes or events beyond the global middleware span. |---------|---------------------|-------| | `init` | `init.method` (template / app / project / environment / copilot), `appinit.detected.databases`, `appinit.detected.services`, `appinit.confirmed.databases`, `appinit.confirmed.services`, `appinit.modify_add.count`, `appinit.modify_remove.count`, `appinit.lastStep` | Comprehensive coverage via `SetUsageAttributes` and `repository/app_init.go` | | `update` | `update.installMethod`, `update.channel`, `update.fromVersion`, `update.toVersion`, `update.result` | Result codes cover success, failure, and skip reasons | -| Extensions (dynamic) | `extension.id`, `extension.version`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.source.kind`, `extension.source.from`, `extension.source.to`, `extension.dependency_of`, `extension.dependency_upgrade_count`, `extension.upgrade.outcome`, `extension.upgrade.duration_ms` + trace-context propagation to child process | Covers `ext.run`, `ext.install`, `ext.upgrade`, `ext.promote` events; `extension.source.kind` distinguishes no source, registered source, and direct location usage for extension list/show/install/upgrade | +| Extensions (dynamic) | `extension.id`, `extension.version`, `extension.version.from`, `extension.version.to`, `extension.source`, `extension.source.kind`, `extension.source.from`, `extension.source.to`, `extension.dependency_of`, `extension.dependency_update_count`, `extension.update.outcome`, `extension.update.duration_ms` + trace-context propagation to child process | Covers `ext.run`, `ext.install`, `ext.update`, `ext.promote` events; `extension.source.kind` distinguishes no source, registered source, and direct location usage for extension list/show/install/update | | `mcp start` | Per-tool spans via `tracing.Start` with `mcp.client.name`, `mcp.client.version` | MCP event prefix `mcp.*` | -| `tool install` / `tool update` / `tool uninstall` / `tool check` / `tool list` / `tool show` | `tool.id`, `tool.ids`, `tool.dry_run`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.upgrade.from_version`, `tool.upgrade.to_version`, `tool.check.updates_available` | Comprehensive coverage in `cli/azd/cmd/tool.go`; install/update emit `tools.pack.build` spans for pack-based tools | +| `tool install` / `tool update` / `tool uninstall` / `tool check` / `tool list` / `tool show` | `tool.id`, `tool.ids`, `tool.dry_run`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.update.from_version`, `tool.update.to_version`, `tool.check.updates_available` | Comprehensive coverage in `cli/azd/cmd/tool.go`; install/update emit `tools.pack.build` spans for pack-based tools | | `copilot` (agent) | `copilot.initialize` event (model + reasoning config), `copilot.session` event (session create/resume) | Emitted from `internal/agent/copilot_agent.go`; covers the experimental copilot agent surface | | `provision` | `validation.provision` event (provision validation outcome + 6 fields), 8 `arm.*` events (subscription / resource-group deploy / stack-deploy / what-if / validate), `aks.postprovision.skip`, per-layer `provision.layer.*` counts (`count`, `max_parallel`, `safe_fallback_count`, `explicit_dependson_count`) when multi-layer infra is used | Telemetry added across `internal/cmd/provision_*.go` and the ARM deployment client | | `deploy` / `publish` / `package` | `deploy.appservice.zip` event (zip-deploy outcome), `container.credentials` / `container.publish` / `container.remotebuild` events for container-based services | Per-service-target instrumentation; container events emitted from container-app and ACR push paths | @@ -87,7 +87,7 @@ These commands emit attributes or events beyond the global middleware span. | **Copilot Consent** | | | | | | | `copilot consent` | `list`, `revoke`, `grant` | ✅ | ❌ | ❌ | Low priority | | **Extension Management** | | | | | | -| `extension` | `list`, `show`, `install`, `uninstall`, `update` | ✅ | ✅ | ✅ | Covered by `extension.*` fields and `ext.install`, `ext.upgrade`, `ext.promote` events; `extension.source.kind` tracks `--source` argument kind for list/show/install/update | +| `extension` | `list`, `show`, `install`, `uninstall`, `update` | ✅ | ✅ | ✅ | Covered by `extension.*` fields and `ext.install`, `ext.update`, `ext.promote` events; `extension.source.kind` tracks `--source` argument kind for list/show/install/update | | `extension source` | `list`, `add`, `remove`, `validate` | ✅ | ❌ | ❌ | Subcommand name in the global span captures the operation; `extension.source*` attributes are recorded by `extension update` / `extension promote`, not by this subcommand | | **Init** | | | | | | | `init` | — | ✅ | ✅ | ✅ | Comprehensive coverage via `appinit.*` fields | @@ -98,7 +98,7 @@ These commands emit attributes or events beyond the global middleware span. | **Tool Management** | | | | | | | `tool list` | — | ✅ | ✅ | ❌ | `tool.ids` listed for visibility into per-row outputs | | `tool install` | — | ✅ | ✅ | ✅ | `tool.id`, `tool.install.strategy`, `tool.install.success`, `tool.install.success_count`, `tool.install.failure_count`, `tool.install.failed_ids`, `tool.install.duration_ms`, `tool.dry_run`; `tools.pack.build` for pack-based tools | -| `tool update` | — | ✅ | ✅ | ✅ | All `tool.install.*` plus `tool.upgrade.from_version`, `tool.upgrade.to_version` | +| `tool update` | — | ✅ | ✅ | ✅ | All `tool.install.*` plus `tool.update.from_version`, `tool.update.to_version` | | `tool uninstall` | — | ✅ | ✅ | ✅ | `tool.id`/`tool.ids`, `tool.dry_run`, and the `tool.install.*` aggregate/per-tool fields (no version fields) | | `tool check` | — | ✅ | ✅ | ❌ | `tool.check.updates_available` (count) | | `tool show` | — | ✅ | ✅ | ❌ | `tool.id` | @@ -125,7 +125,7 @@ command-specific telemetry fields provide analytical value beyond the command na | Infra provider | `infra.provider` | `infra generate`, `infra synth`, `provision`, `up`, `down` | provision/up/down: sorted, de-duplicated string slice of resolved providers — `bicep`/`terraform`/`arm`/`pulumi` verbatim, `custom` for extension providers (raw name not emitted); multi-layer projects that combine providers record each distinct value (e.g. `["bicep","terraform"]`). `infra generate`/`synth`: the value read from azure.yaml's `infra.provider` emitted directly as a single string (`bicep`/`terraform`/`arm`/`pulumi`, `auto` when unset, or `custom` for extension providers — raw name not emitted) | | Tool ID | `tool.id` / `tool.ids` | `tool *` | Identifies which managed tool (e.g., bicep, gh, kubectl) the command acted on | | Tool install metrics | `tool.install.*` | `tool install`, `tool update`, `tool uninstall` | Success count, failure count, duration, strategy — quantitative install health | -| Tool update versions | `tool.upgrade.from_version`, `tool.upgrade.to_version` | `tool update` | Tracks adoption of new tool versions | +| Tool update versions | `tool.update.from_version`, `tool.update.to_version` | `tool update` | Tracks adoption of new tool versions | | Provision validation outcome | `validation.provision.outcome` (+ peer fields incl. `validation.provision.check_type`) | `provision` | Distinguishes passed / warnings-accepted / canceled local validation; `check_type` separates the provider-agnostic `provision` dispatch from the Bicep `arm-provision` dispatch (both share the event) | | ARM deployment events | `arm.deploy.*`, `arm.stack.deploy.*`, `arm.whatif.*`, `arm.validate.*` | `provision` | Distinguishes deployment scope (subscription vs resource-group) and operation kind (deploy / stack / what-if / validate) | | Container events | `container.credentials`, `container.publish`, `container.remotebuild` | `package`, `deploy` | Per-stage container lifecycle for container-based services | diff --git a/docs/specs/metrics-audit/telemetry-schema.md b/docs/specs/metrics-audit/telemetry-schema.md index 2fa1b8a66cc..11fcb9042b4 100644 --- a/docs/specs/metrics-audit/telemetry-schema.md +++ b/docs/specs/metrics-audit/telemetry-schema.md @@ -17,7 +17,7 @@ OpenTelemetry span name or event name. | `AgentTroubleshootEvent` | `agent.troubleshoot` | Agent troubleshooting event | | `ExtensionRunEvent` | `ext.run` | Extension execution event | | `ExtensionInstallEvent` | `ext.install` | Extension install/upgrade event | -| `ExtensionUpgradeEvent` | `ext.upgrade` | Single extension update attempt | +| `ExtensionUpdateEvent` | `ext.update` | Single extension update attempt | | `ExtensionPromoteEvent` | `ext.promote` | Extension registry promotion (e.g., dev → main) | | `CopilotInitializeEvent` | `copilot.initialize` | Copilot initialization event | | `CopilotSessionEvent` | `copilot.session` | Copilot session lifecycle event | @@ -210,10 +210,10 @@ not emitted by azd spans. | Extension source kind | `extension.source.kind` | SystemMetadata | FeatureInsight | Allowed values: `none`, `registered`, `location` | | Extension source from | `extension.source.from` | SystemMetadata | FeatureInsight | Registry source before a promotion | | Extension source to | `extension.source.to` | SystemMetadata | FeatureInsight | Registry source after a promotion | -| Upgrade duration | `extension.upgrade.duration_ms` | SystemMetadata | PerformanceAndHealth | **Measurement** — time in ms for one upgrade | -| Upgrade outcome | `extension.upgrade.outcome` | SystemMetadata | FeatureInsight | Upgrade result status | +| Update duration | `extension.update.duration_ms` | SystemMetadata | PerformanceAndHealth | **Measurement** — time in ms for one update | +| Update outcome | `extension.update.outcome` | SystemMetadata | FeatureInsight | Update result status | | Dependency of | `extension.dependency_of` | SystemMetadata | FeatureInsight | Parent extension for a dependency upgrade | -| Dependency upgrade count | `extension.dependency_upgrade_count` | SystemMetadata | FeatureInsight | Recursive dependency upgrade count | +| Dependency update count | `extension.dependency_update_count` | SystemMetadata | FeatureInsight | Recursive dependency update count | ### Update @@ -322,8 +322,8 @@ remain defined to support a possible future redesign without changing the teleme | Install failure count | `tool.install.failure_count` | SystemMetadata | FeatureInsight | **Measurement** — number of tools that failed in a batch | | Install failed IDs | `tool.install.failed_ids` | SystemMetadata | FeatureInsight | Comma-separated built-in tool IDs whose install/upgrade failed. Per-tool error messages are intentionally not captured | | Install duration | `tool.install.duration_ms` | SystemMetadata | FeatureInsight | **Measurement** — total install/upgrade duration in ms | -| Upgrade from version | `tool.upgrade.from_version` | SystemMetadata | FeatureInsight | Prior version (single-target upgrades) | -| Upgrade to version | `tool.upgrade.to_version` | SystemMetadata | FeatureInsight | New version after a successful upgrade | +| Update from version | `tool.update.from_version` | SystemMetadata | FeatureInsight | Prior version (single-target updates) | +| Update to version | `tool.update.to_version` | SystemMetadata | FeatureInsight | New version after a successful update | | Updates available | `tool.check.updates_available` | SystemMetadata | FeatureInsight | **Measurement** — number of installed tools with an available upgrade | ### Provision Validation