Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .agents/skills/uloop-pause-point/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
name: uloop-pause-point
toolName: await-pause-point
description: "Pauses Unity playback at any source file:line without editing code or recompiling, and returns a snapshot of the locals, parameters, and instance fields at that exact frame. Use for bug investigation, PlayMode/E2E verification, checking variable values at a specific frame, or confirming that a code path executed."
---

Expand Down
1 change: 0 additions & 1 deletion .claude/skills/uloop-pause-point/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
name: uloop-pause-point
toolName: await-pause-point
description: "Pauses Unity playback at any source file:line without editing code or recompiling, and returns a snapshot of the locals, parameters, and instance fields at that exact frame. Use for bug investigation, PlayMode/E2E verification, checking variable values at a specific frame, or confirming that a code path executed."
---

Expand Down
2 changes: 1 addition & 1 deletion Assets/Tests/Editor/SkillInstallLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public void GetToolDescriptionsByToolName_WhenSkillHasDescription_MapsDescriptio
IReadOnlyDictionary<string, string> descriptions = SkillInstallLayout.GetToolDescriptionsByToolName(_projectRoot);

Assert.That(descriptions["compile"], Is.EqualTo("Compile the Unity project and report errors/warnings. Use after C# edits."));
Assert.That(descriptions[UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT], Does.StartWith("Pauses Unity playback"));
Assert.That(descriptions[UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT], Does.StartWith("Pauses Unity playback"));
}

// Tests that duplicate skill names use the earlier source root across each precedence boundary.
Expand Down
74 changes: 64 additions & 10 deletions Assets/Tests/Editor/ToolSettingsUseCaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor
public class ToolSettingsUseCaseTests
{
[Test]
public void TryGetToolCatalog_WhenRegistryAvailable_ShowsOnlyWaitForPausePointCommand()
public void TryGetToolCatalog_WhenRegistryAvailable_ShowsOnlyPausePointSettingsTool()
{
// Verifies Tool Settings exposes only the parent pause point command.
// Verifies Tool Settings exposes only the pause-point family toggle.
IToolSettingsPort toolSettingsPort = new InMemoryToolSettingsPort();
UnityCliLoopToolRegistrarService toolRegistrarService = new(
new EmptyInternalToolNameProvider(),
Expand All @@ -36,16 +36,17 @@ public void TryGetToolCatalog_WhenRegistryAvailable_ShowsOnlyWaitForPausePointCo
string[] toolNames = allTools.Select(tool => tool.Name).ToArray();

Assert.That(isAvailable, Is.True);
Assert.That(toolNames, Does.Contain(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT));
Assert.That(toolNames, Does.Contain(UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT));
Assert.That(toolNames, Does.Not.Contain(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT));
Assert.That(toolNames, Does.Not.Contain(UnityCliLoopConstants.TOOL_NAME_ENABLE_PAUSE_POINT));
Assert.That(toolNames, Does.Not.Contain(UnityCliLoopConstants.TOOL_NAME_CLEAR_PAUSE_POINT));
Assert.That(toolNames, Does.Not.Contain(UnityCliLoopConstants.COMMAND_NAME_PAUSE_POINT_STATUS));
}

[Test]
public void IsToolEnabled_WhenWaitForPausePointDisabled_DisablesPausePointAuxiliaryTools()
public void IsToolEnabled_WhenPausePointDisabled_DisablesPausePointAuxiliaryTools()
{
// Verifies pause point auxiliary tools follow the await-pause-point setting.
// Verifies pause point auxiliary tools follow the pause-point settings toggle.
IToolSettingsPort toolSettingsPort = new InMemoryToolSettingsPort();
UnityCliLoopToolRegistrarService toolRegistrarService = new(
new EmptyInternalToolNameProvider(),
Expand All @@ -57,14 +58,50 @@ public void IsToolEnabled_WhenWaitForPausePointDisabled_DisablesPausePointAuxili
toolRegistrarService,
new StaticToolSkillDescriptionProvider(new Dictionary<string, string>()));

useCase.SetToolEnabled(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT, false);
useCase.SetToolEnabled(UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT, false);

Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT), Is.False);
Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT), Is.False);
Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.TOOL_NAME_ENABLE_PAUSE_POINT), Is.False);
Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.TOOL_NAME_CLEAR_PAUSE_POINT), Is.False);
Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.COMMAND_NAME_PAUSE_POINT_STATUS), Is.False);
}

[Test]
public void IsToolEnabled_WhenAwaitPausePointDisabled_DisablesPausePointAuxiliaryTools()
{
// Verifies disabling via the await-pause-point command name still maps to the pause-point toggle.
IToolSettingsPort toolSettingsPort = new InMemoryToolSettingsPort();
UnityCliLoopToolRegistrarService toolRegistrarService = new(
new EmptyInternalToolNameProvider(),
toolSettingsPort,
new UnityCliLoopToolExecutionService(new NoOpEditorRuntimeStatePort()),
UnityCliLoopToolDiscovery.DiscoverTools);
ToolSettingsUseCase useCase = new(
toolSettingsPort,
toolRegistrarService,
new StaticToolSkillDescriptionProvider(new Dictionary<string, string>()));

useCase.SetToolEnabled(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT, false);

Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT), Is.False);
Assert.That(useCase.IsToolEnabled(UnityCliLoopConstants.COMMAND_NAME_PAUSE_POINT_STATUS), Is.False);
}

[Test]
public void ToolSettingsToolLinkPolicy_WhenAwaitPausePointRequested_IsNotUserFacingAndMapsToPausePoint()
{
// Verifies await-pause-point is auxiliary and resolves to the pause-point settings key.
Assert.That(
ToolSettingsToolLinkPolicy.IsUserFacingToolSettingsTool(
UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT),
Is.False);
Assert.That(
ToolSettingsToolLinkPolicy.GetSettingsToolName(
UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT),
Is.EqualTo(UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT));
}

[Test]
public void TryGetToolCatalog_WhenSkillDescriptionExists_IncludesDescription()
{
Expand All @@ -77,7 +114,7 @@ public void TryGetToolCatalog_WhenSkillDescriptionExists_IncludesDescription()
UnityCliLoopToolDiscovery.DiscoverTools);
Dictionary<string, string> descriptions = new()
{
[UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT] = "Pause point description"
[UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT] = "Pause point description"
};
ToolSettingsUseCase useCase = new(
toolSettingsPort,
Expand All @@ -86,11 +123,28 @@ public void TryGetToolCatalog_WhenSkillDescriptionExists_IncludesDescription()
useCase.WarmupRegistry();

bool isAvailable = useCase.TryGetToolCatalog(out ToolSettingsUseCase.ToolCatalogItem[] allTools);
ToolSettingsUseCase.ToolCatalogItem waitTool = allTools
.Single(tool => tool.Name == UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT);
ToolSettingsUseCase.ToolCatalogItem pausePointTool = allTools
.Single(tool => tool.Name == UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT);

Assert.That(isAvailable, Is.True);
Assert.That(waitTool.SkillDescription, Is.EqualTo("Pause point description"));
Assert.That(pausePointTool.SkillDescription, Is.EqualTo("Pause point description"));
}

[Test]
public void IsSkillDisabledByToolSettings_WhenPausePointDisabled_ReturnsTrueForPausePointSkill()
{
// Verifies disabledTools pause-point key matches the pause-point skill tool name.
SkillInstallLayout.SkillSourceInfo skill = new(
"uloop-pause-point",
UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT,
new Dictionary<string, byte[]>());
string[] disabledTools = { UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT };

bool isDisabled = SkillDisabledToolFilter.IsSkillDisabledByToolSettings(
skill,
disabledTools);

Assert.That(isDisabled, Is.True);
}

private sealed class StaticToolSkillDescriptionProvider : IToolSkillDescriptionProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal sealed class ToolSettingsUseCase
{
private static readonly string[] NativeToolNames =
{
UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT
UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT
};

private readonly IToolSettingsPort _toolSettingsPort;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
name: uloop-pause-point
toolName: await-pause-point
description: "Pauses Unity playback at any source file:line without editing code or recompiling, and returns a snapshot of the locals, parameters, and instance fields at that exact frame. Use for bug investigation, PlayMode/E2E verification, checking variable values at a specific frame, or confirming that a code path executed."
---

Expand Down
3 changes: 2 additions & 1 deletion Packages/src/Editor/Domain/ToolSettingsToolLinkPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal static class ToolSettingsToolLinkPolicy
{
private static readonly HashSet<string> PausePointAuxiliaryToolNames = new(System.StringComparer.Ordinal)
{
UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT,
UnityCliLoopConstants.TOOL_NAME_ENABLE_PAUSE_POINT,
UnityCliLoopConstants.TOOL_NAME_CLEAR_PAUSE_POINT,
UnityCliLoopConstants.COMMAND_NAME_PAUSE_POINT_STATUS
Expand All @@ -38,7 +39,7 @@ internal static string GetSettingsToolName(string toolName)

if (PausePointAuxiliaryToolNames.Contains(toolName))
{
return UnityCliLoopConstants.COMMAND_NAME_AWAIT_PAUSE_POINT;
return UnityCliLoopConstants.SETTINGS_TOOL_NAME_PAUSE_POINT;
}

return toolName;
Expand Down
1 change: 1 addition & 0 deletions Packages/src/Editor/ToolContracts/UnityCliLoopConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public static UnityEditor.PackageManager.PackageInfo PackageInfo
public const string COMMAND_NAME_GET_VERSION = "get-version";
public const string COMMAND_NAME_GET_COMPILE_STATUS = "get-compile-status";
public const string COMMAND_NAME_AWAIT_PAUSE_POINT = "await-pause-point";
public const string SETTINGS_TOOL_NAME_PAUSE_POINT = "pause-point";
public const string COMMAND_NAME_PAUSE_POINT_STATUS = "pause-point-status";
public const string COMMAND_NAME_GET_PAUSE_POINT_STATUS = "get-pause-point-status";
public const string COMMAND_NAME_CLEAR_PAUSE_POINT_STATUS = "clear-pause-point-status";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package projectrunner

import (
"github.com/hatayama/unity-cli-loop/common/clicore"
clierrors "github.com/hatayama/unity-cli-loop/common/errors"

"github.com/hatayama/unity-cli-loop/common/clicore"
)

func isSettingsManagedNativeToolCommand(command string) bool {
const pausePointSettingsToolName = "pause-point"

func settingsToolNameForNativeCommand(command string) (string, bool) {
switch command {
case clicore.PausePointAwaitCommandName, clicore.PausePointStatusUserCommandName:
return true
return pausePointSettingsToolName, true
default:
return false
return "", false
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ func TestPausePointExpiredErrorReportsNoRemainingTime(t *testing.T) {
// Verifies disabled native pause-point commands are rejected before Unity dispatch.
func TestRunProjectLocalWaitForPausePointRespectsToolSettings(t *testing.T) {
projectRoot := createLaunchTestProject(t)
writeToolSettings(t, projectRoot, `{"disabledTools":["await-pause-point"]}`)
writeToolSettings(t, projectRoot, `{"disabledTools":["pause-point"]}`)
t.Chdir(filepath.Dir(projectRoot))

var stdout bytes.Buffer
Expand All @@ -852,6 +852,32 @@ func TestRunProjectLocalWaitForPausePointRespectsToolSettings(t *testing.T) {
}
}

// Verifies disabled pause-point-status is rejected before Unity dispatch.
func TestRunProjectLocalPausePointStatusRespectsToolSettings(t *testing.T) {
projectRoot := createLaunchTestProject(t)
writeToolSettings(t, projectRoot, `{"disabledTools":["pause-point"]}`)
t.Chdir(filepath.Dir(projectRoot))

var stdout bytes.Buffer
var stderr bytes.Buffer
code := RunProjectLocal(
context.Background(),
[]string{"--project-path", projectRoot, clicore.PausePointStatusUserCommandName, "--id", "jump"},
&stdout,
&stderr)

if code != 1 {
t.Fatalf("expected disabled command failure, got %d with stdout %s", code, stdout.String())
}
envelope := parsePausePointErrorEnvelope(t, stderr.Bytes())
if envelope.Error.ErrorCode != clierrors.ErrorCodeToolDisabled {
t.Fatalf("error code mismatch: %#v", envelope.Error)
}
if envelope.Error.Command != clicore.PausePointStatusUserCommandName {
t.Fatalf("command mismatch: %#v", envelope.Error)
}
}

// Verifies await-pause-point requires a marker id.
func TestParseWaitForPausePointOptionsRequiresID(t *testing.T) {
_, err := parseWaitForPausePointOptions([]string{"--timeout-seconds", "1"})
Expand Down
4 changes: 2 additions & 2 deletions cli/project-runner/internal/projectrunner/runner_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func runResolvedProjectCommand(
stdout io.Writer,
stderr io.Writer,
) int {
if isSettingsManagedNativeToolCommand(command) &&
clicore.IsToolDisabledByToolSettings(command, clicore.LoadDisabledTools(connection.ProjectRoot)) {
if settingsToolName, isSettingsManaged := settingsToolNameForNativeCommand(command); isSettingsManaged &&
clicore.IsToolDisabledByToolSettings(settingsToolName, clicore.LoadDisabledTools(connection.ProjectRoot)) {
clierrors.WriteErrorEnvelope(stderr, nativeToolDisabledError(connection.ProjectRoot, command))
return 1
}
Expand Down
Loading