From 3bbf8883a032678c9a9a0657c243c6bf69193ac0 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 12 Jul 2026 11:42:14 +0900 Subject: [PATCH 1/2] feat: Prompt users to remove the temporary V3 migration skill when done The temporary skill can linger in agent folders after docs and scripts are migrated. Auto-removal is unsafe because C# scan results do not mean the skill is finished, so guide removal instead: the skill workflow reports the uninstall command when searches find zero V2 candidates, and the migration wizard shows a temporary-skill note while it is installed. --- ...hirdPartyToolMigrationWizardWindowTests.cs | 13 ++++++++++ ...ThirdPartyToolMigrationWizardStateRules.cs | 10 ++++++++ .../ThirdPartyToolMigrationWizardText.cs | 2 ++ .../ThirdPartyToolMigrationWizardView.cs | 25 ++++++++++++++++--- .../Skill/SKILL.md | 4 +++ 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs index a4c4e1897..492f2923c 100644 --- a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs +++ b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs @@ -464,6 +464,19 @@ public void ShouldRemoveMigrationSkill_ReturnsExpectedValue( Assert.That(shouldRemove, Is.EqualTo(expected)); } + [TestCase(SkillInstallState.Installed, true)] + [TestCase(SkillInstallState.Outdated, true)] + [TestCase(SkillInstallState.Missing, false)] + public void ShouldShowTemporarySkillNote_ReturnsExpectedValue( + SkillInstallState installState, + bool expected) + { + // Verifies the temporary skill note shows only while the skill is installed. + bool shouldShow = ThirdPartyToolMigrationWizardStateRules.ShouldShowTemporarySkillNote(installState); + + Assert.That(shouldShow, Is.EqualTo(expected)); + } + [TestCase(0, 1, 1, 4, 1000, 100, true)] [TestCase(10, 50, 1, 4, 1000, 100, false)] [TestCase(10, 110, 1, 4, 1000, 100, true)] diff --git a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardStateRules.cs b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardStateRules.cs index 1076ce24d..a1581f953 100644 --- a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardStateRules.cs +++ b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardStateRules.cs @@ -99,5 +99,15 @@ internal static bool ShouldRefreshAfterInterruptedMigration( { return isMigrationCompletionPending && !isCancellationRequested; } + + /// + /// Returns whether the temporary V3 migration skill note should be visible. + /// + internal static bool ShouldShowTemporarySkillNote(SkillInstallState installState) + { + // C# scan results are not a completion signal for this skill (docs/scripts remain + // in scope), so only the install state decides whether the temporary note shows. + return installState != SkillInstallState.Missing; + } } } diff --git a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardText.cs b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardText.cs index da1c3d710..5449b6c1e 100644 --- a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardText.cs +++ b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardText.cs @@ -19,6 +19,8 @@ internal static class ThirdPartyToolMigrationWizardText "that call uloop.\nThe skill teaches your AI agent how to search, inspect context, and update only " + "real V2 CLI usage. This window only installs or removes it."; internal const string AiMigrationSkillUsageFoldoutTitle = "Prompt for your AI agent"; + internal const string AiMigrationSkillTemporaryNoteText = + "This skill is temporary. Remove it once your docs and scripts are migrated to V3 CLI syntax."; internal const string AiMigrationSkillPromptText = "Use the v3-cli-invocation-migration skill in this project to update Unity CLI Loop V2 CLI usage " + "for V3.\n\n" + diff --git a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardView.cs b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardView.cs index 68ead776f..48c5d54eb 100644 --- a/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardView.cs +++ b/Packages/src/Editor/Presentation/Setup/ThirdPartyToolMigrationWizardView.cs @@ -22,6 +22,7 @@ internal sealed class ThirdPartyToolMigrationWizardView private readonly Button _migrateButton; private readonly EnumField _migrationSkillTargetField; private readonly Button _migrationSkillButton; + private readonly TextField _migrationSkillTemporaryNoteTextField; private readonly Button _refreshButton; private readonly Button _closeButton; @@ -33,6 +34,7 @@ private ThirdPartyToolMigrationWizardView( Button migrateButton, EnumField migrationSkillTargetField, Button migrationSkillButton, + TextField migrationSkillTemporaryNoteTextField, Button refreshButton, Button closeButton) { @@ -43,6 +45,9 @@ private ThirdPartyToolMigrationWizardView( Debug.Assert(migrateButton != null, "migrateButton must not be null"); Debug.Assert(migrationSkillTargetField != null, "migrationSkillTargetField must not be null"); Debug.Assert(migrationSkillButton != null, "migrationSkillButton must not be null"); + Debug.Assert( + migrationSkillTemporaryNoteTextField != null, + "migrationSkillTemporaryNoteTextField must not be null"); Debug.Assert(refreshButton != null, "refreshButton must not be null"); Debug.Assert(closeButton != null, "closeButton must not be null"); @@ -53,6 +58,7 @@ private ThirdPartyToolMigrationWizardView( _migrateButton = migrateButton; _migrationSkillTargetField = migrationSkillTargetField; _migrationSkillButton = migrationSkillButton; + _migrationSkillTemporaryNoteTextField = migrationSkillTemporaryNoteTextField; _refreshButton = refreshButton; _closeButton = closeButton; } @@ -87,7 +93,7 @@ internal static ThirdPartyToolMigrationWizardView Create( Button migrateButton = CreateMigrateButton(migrationButtonRow); (Button refreshButton, Button closeButton) = CreateCSharpMigrationActionSection(mainScrollView); CreateSectionDivider(mainScrollView); - (EnumField migrationSkillTargetField, Button migrationSkillButton) = + (EnumField migrationSkillTargetField, Button migrationSkillButton, TextField temporaryNoteTextField) = CreateAiMigrationSkillSection(mainScrollView); CreateFooter(mainScrollView); @@ -99,6 +105,7 @@ internal static ThirdPartyToolMigrationWizardView Create( migrateButton, migrationSkillTargetField, migrationSkillButton, + temporaryNoteTextField, refreshButton, closeButton); view.BindEvents(refresh, migrate, migrationSkillTargetChanged, toggleMigrationSkill, close); @@ -116,6 +123,9 @@ internal void SetMigrationSkillState( installState); _migrationSkillTargetField.SetEnabled(!isUpdating); _migrationSkillButton.SetEnabled(!isUpdating); + ViewDataBinder.SetVisible( + _migrationSkillTemporaryNoteTextField, + ThirdPartyToolMigrationWizardStateRules.ShouldShowTemporarySkillNote(installState)); } internal void ShowNotCheckedState(bool isMigrating) @@ -284,7 +294,7 @@ private static void CreateSectionDivider(ScrollView mainScrollView) mainScrollView.Add(divider); } - private static (EnumField migrationSkillTargetField, Button migrationSkillButton) + private static (EnumField migrationSkillTargetField, Button migrationSkillButton, TextField temporaryNoteTextField) CreateAiMigrationSkillSection(ScrollView mainScrollView) { VisualElement migrationSkillSection = new VisualElement(); @@ -334,9 +344,18 @@ private static (EnumField migrationSkillTargetField, Button migrationSkillButton migrationSkillButton.AddToClassList("setup-button"); migrationSkillButton.AddToClassList("setup-button--primary"); buttonRow.Add(migrationSkillButton); + + TextField temporaryNoteTextField = CreateSelectableText( + ThirdPartyToolMigrationWizardText.AiMigrationSkillTemporaryNoteText, + "setup-step__description-label"); + actionSection.Add(temporaryNoteTextField); + // Missing is the default create-time install state; hide until SetMigrationSkillState + // reports an installed or outdated skill. + ViewDataBinder.SetVisible(temporaryNoteTextField, false); + CreateMigrationSkillUsageFoldout(actionSection); CreateMigrationSkillPromptCopyButton(actionSection); - return (migrationSkillTargetField, migrationSkillButton); + return (migrationSkillTargetField, migrationSkillButton, temporaryNoteTextField); } private static void CreateMigrationSkillUsageFoldout(VisualElement content) diff --git a/Packages/src/TemporarySkills~/v3-cli-invocation-migration/Skill/SKILL.md b/Packages/src/TemporarySkills~/v3-cli-invocation-migration/Skill/SKILL.md index bfad39dbe..100e39c33 100644 --- a/Packages/src/TemporarySkills~/v3-cli-invocation-migration/Skill/SKILL.md +++ b/Packages/src/TemporarySkills~/v3-cli-invocation-migration/Skill/SKILL.md @@ -18,6 +18,10 @@ syntax in agent-facing docs and automation. 4. Edit only files that clearly contain V2 `uloop` option syntax. 5. Repeat the searches after editing and report any remaining V2 candidates with the reason each one was left unchanged. +6. After every Required Search Pass reports zero remaining V2 candidates, + tell the user this temporary skill has finished its job and can be + removed with `uloop skills uninstall-v3-migration --` (for + example `--claude` or `--codex`). Do not uninstall the skill yourself. Prefer `rg` for searches when available. If `rg` is unavailable, use the best available project search tool. Candidate discovery should be repository search plus context inspection, not a generated candidate list. From 80a647c496382d343c5d64e33b0da60782573ed4 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 12 Jul 2026 11:49:18 +0900 Subject: [PATCH 2/2] test: Cover Checking state for temporary migration skill note CodeRabbit noted that ShouldShowTemporarySkillNote returns true for Checking while the button still shows Install. Lock that transitional behavior with an explicit test case. --- .../Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs index 492f2923c..f1fb1273c 100644 --- a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs +++ b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs @@ -466,12 +466,13 @@ public void ShouldRemoveMigrationSkill_ReturnsExpectedValue( [TestCase(SkillInstallState.Installed, true)] [TestCase(SkillInstallState.Outdated, true)] + [TestCase(SkillInstallState.Checking, true)] [TestCase(SkillInstallState.Missing, false)] public void ShouldShowTemporarySkillNote_ReturnsExpectedValue( SkillInstallState installState, bool expected) { - // Verifies the temporary skill note shows only while the skill is installed. + // Verifies the temporary skill note shows for every non-missing install state. bool shouldShow = ThirdPartyToolMigrationWizardStateRules.ShouldShowTemporarySkillNote(installState); Assert.That(shouldShow, Is.EqualTo(expected));