Editor/Tools/WorkflowTools.SceneCleanup.cs

Editor tool method that scans the active scene for GameObjects whose names match given prefixes or have a specified tag, optionally destroys them, and returns a JSON summary of candidates and results.

File Access
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Sandbox;

namespace Editor.Mcp
{
	public static partial class SboxMcpAssistant
	{
		/// <summary>
		/// Scans and purges temporary test GameObjects, procedural hazard clutter, orbit cameras,
		/// and orphaned test entities created during automated test iterations.
		/// </summary>
		[McpTool( "pocketknife_scene_cleanup" )]
		public static string SceneCleanup(
			[Description( "Comma-separated list of name prefixes to purge (default: '__temp_,Procedural_,Test_,Auto_,OrbitCamera_,Brush_,Audio_')" )] string prefixes = "__temp_,Procedural_,Test_,Auto_,OrbitCamera_,Brush_,Audio_",
			[Description( "Optional tag filter (e.g. 'test' or 'temporary')" )] string? tag = null,
			[Description( "If true, simulates the purge and returns candidates without destroying them" )] bool dryRun = false )
		{
			try
			{
				var scene = EditorRuntime.ResolveActiveScene();
				if ( scene == null )
				{
					return McpJson.Write( ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "scene.not_found", "No active scene loaded." ) );
				}

				var prefixList = (prefixes ?? "")
					.Split( new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries )
					.Select( p => p.Trim() )
					.Where( p => p.Length > 0 )
					.ToArray();

				var allObjects = scene.GetAllObjects( false ).ToList();
				var toPurge = new List<GameObject>();

				foreach ( var go in allObjects )
				{
					if ( !go.IsValid() ) continue;

					var matchesPrefix = prefixList.Any( p => go.Name.StartsWith( p, StringComparison.OrdinalIgnoreCase ) );
					var matchesTag = !string.IsNullOrWhiteSpace( tag ) && go.Tags.Has( tag.Trim() );

					if ( matchesPrefix || matchesTag )
					{
						toPurge.Add( go );
					}
				}

				var purgedList = toPurge.Select( go => new
				{
					Name = go.Name,
					Id = go.Id.ToString(),
					Position = go.WorldPosition,
					Tags = string.Join( ", ", go.Tags.TryGetAll() )
				} ).ToArray();

				if ( !dryRun )
				{
					foreach ( var go in toPurge )
					{
						if ( go.IsValid() )
						{
							go.Destroy();
						}
					}
				}

				return McpJson.Write( ToolOutcome<object>.Ready( new
				{
					DryRun = dryRun,
					TotalPurged = purgedList.Length,
					PrefixesUsed = prefixList,
					TagFilter = tag,
					PurgedObjects = purgedList,
					Success = true
				} ) );
			}
			catch ( Exception ex )
			{
				return McpJson.Write( ToolOutcome<object>.Failure( null, ToolStatus.Failed, "cleanup.failed", "Scene cleanup operation failed.", ex ) );
			}
		}
	}
}