using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

using AIRuntime.Runtime.Agents;
using AIRuntime.Runtime.Agents.Runtime;
using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Permissions;
using AIRuntime.Runtime.Filesystem;
using AIRuntime.Runtime.Providers;
using AIRuntime.Runtime.Providers.Fallback;
using AIRuntime.Runtime.Providers.NousPortal;
using AIRuntime.Runtime.Providers.Nvidia;
using AIRuntime.Runtime.Providers.Ollama;
using AIRuntime.Runtime.Providers.OpenAI;
using AIRuntime.Runtime.Providers.OpenRouter;
using AIRuntime.Runtime.Services.Agent;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using UnityEditor;
using UnityEngine;

using RuntimeFacade =
    AIRuntime.Runtime.AIRuntime;


namespace AIRuntime.Editor
{
    internal static class AIRuntimeKanbanAgentRunner
    {
        private sealed class KanbanArtifactOwnershipMiddleware :
            ICapabilityMiddleware
        {
            private sealed class CompilationNoProgressException : Exception
            {
                public CompilationNoProgressException(string message)
                    : base(message)
                {
                }
            }

            private readonly HashSet<string> _ownedArtifactPaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private readonly HashSet<string> _mutatedArtifactPaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private readonly HashSet<string> _mutableScenePaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private readonly HashSet<string> _pendingScriptImportPaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private readonly HashSet<string> _failedScriptImportPaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private readonly Dictionary<string, string[]> _scriptImportOperations =
                new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

            private readonly HashSet<string> _readableArtifactPaths =
                new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            private string _lastCompilerErrorFingerprint;
            private int _unchangedCompilerErrorReads;
            private readonly HashSet<string> _observedCompilerDiagnosticIdentities =
                new HashSet<string>(StringComparer.Ordinal);

            private static readonly HashSet<string> RequiresCurrentCompiledTypes =
                new HashSet<string>(
                    new[]
                    {
                        "create_game_object",
                        "save_editor_scene",
                        "create_prefab",
                        "instantiate_prefab",
                        "apply_prefab_overrides",
                        "revert_or_unpack_prefab",
                        "inspect_game_object",
                        "find_game_objects",
                        "inspect_scene",
                        "inspect_prefab",
                        "get_editor_selection",
                        "add_editor_component",
                        "remove_editor_component",
                        "inspect_editor_component",
                        "get_serialized_properties",
                        "set_serialized_property",
                        "edit_serialized_collection",
                        "control_play_mode",
                        "inspect_gameplay_readiness",
                        "run_edit_mode_tests",
                        "run_play_mode_tests",
                        "build_player"
                    },
                    StringComparer.OrdinalIgnoreCase);

            private static readonly HashSet<string> MutatesUnityOrProjectState =
                new HashSet<string>(
                    new[]
                    {
                        "create_editor_scene",
                        "create_game_object",
                        "save_editor_scene",
                        "update_game_object",
                        "duplicate_game_object",
                        "destroy_game_object",
                        "instantiate_prefab",
                        "apply_prefab_overrides",
                        "revert_or_unpack_prefab",
                        "add_editor_component",
                        "remove_editor_component",
                        "set_serialized_property",
                        "edit_serialized_collection",
                        "configure_project_settings",
                        "add_unity_package",
                        "remove_unity_package",
                        "set_asset_importer_property"
                    },
                    StringComparer.OrdinalIgnoreCase);

            private bool _enforce;
            private bool _sourceOnlyAuthority;


            private readonly Dictionary<string, string> _siblingOwners =
                new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            public void SetSiblingOwnershipContext(string context)
            {
                _siblingOwners.Clear();
                if (string.IsNullOrWhiteSpace(context)) return;
                foreach (JObject sibling in (JObject.Parse(context)["SiblingContracts"] as JArray ?? new JArray()).OfType<JObject>())
                    foreach (string path in (sibling["ProducedArtifactPaths"] as JArray ?? new JArray()).Values<string>())
                        if (!string.IsNullOrWhiteSpace(path))
                            _siblingOwners[NormalizePath(path)] = $"'{sibling["Title"]}' ({sibling["TaskId"]})";
            }

            private string DescribeOwnershipRecovery(string path)
            {
                return _siblingOwners.TryGetValue(NormalizePath(path), out string owner)
                    ? $" Canonical owner: {owner}. Leave this mutation to that card and continue your owned outputs. " +
                      "An unfinished downstream artifact is not a defect in your card; report a planning conflict only if your own acceptance criteria explicitly require this mutation."
                    : " Continue within your declared outputs. If this artifact is required and has no owner, report the concrete missing ownership to Planning.";
            }

            public void Activate(
                IEnumerable<string> ownedArtifactPaths,
                IEnumerable<string> readableArtifactPaths,
                IEnumerable<string> mutatedResourceKeys,
                bool enforce)
            {
                _sourceOnlyAuthority = IsSourceArtifactContract(ownedArtifactPaths, mutatedResourceKeys);
                _ownedArtifactPaths.Clear();
                _mutatedArtifactPaths.Clear();
                _mutableScenePaths.Clear();
                _pendingScriptImportPaths.Clear();
                _failedScriptImportPaths.Clear();
                _scriptImportOperations.Clear();
                _readableArtifactPaths.Clear();
                _lastCompilerErrorFingerprint = null;
                _unchangedCompilerErrorReads = 0;
                _observedCompilerDiagnosticIdentities.Clear();

                foreach (string path in ownedArtifactPaths ??
                         Enumerable.Empty<string>())
                {
                    string normalized = NormalizePath(path);

                    if (!string.IsNullOrWhiteSpace(normalized))
                    {
                        _ownedArtifactPaths.Add(normalized);
                        _readableArtifactPaths.Add(normalized);
                    }
                }

                foreach (string path in readableArtifactPaths ??
                         Enumerable.Empty<string>())
                {
                    string normalized = NormalizePath(path);

                    if (!string.IsNullOrWhiteSpace(normalized))
                    {
                        _readableArtifactPaths.Add(normalized);
                    }
                }

                foreach (string key in mutatedResourceKeys ??
                         Enumerable.Empty<string>())
                {
                    string normalizedKey =
                        (key ?? string.Empty)
                            .Trim()
                            .Replace('\\', '/');

                    if (!normalizedKey.StartsWith(
                            "scene:",
                            StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    string scenePath =
                        NormalizePath(
                            normalizedKey.Substring("scene:".Length));

                    if (!string.IsNullOrWhiteSpace(scenePath) &&
                        scenePath.EndsWith(
                            ".unity",
                            StringComparison.OrdinalIgnoreCase))
                    {
                        _mutableScenePaths.Add(scenePath);
                        _readableArtifactPaths.Add(scenePath);
                    }
                }

                // A structured verification card with no outputs is still a
                // scoped worker. Treat it as read-only instead of disabling
                // ownership enforcement and accidentally giving it a wider
                // mutation surface than an implementation card.
                _enforce = enforce;
            }


            public async Task InvokeAsync(
                CapabilityContext context,
                CapabilityDelegate next)
            {
                if (_enforce && context?.Capability != null)
                {
                    JObject arguments =
                        context.Arguments == null
                            ? new JObject()
                            : JObject.FromObject(context.Arguments);

                    ValidateMutationTarget(
                        context.Capability.Name,
                        arguments);

                    await next(context);

                    RecordSuccessfulCapability(
                        context.Capability.Name,
                        arguments,
                        context.Result);

                    return;
                }

                await next(context);
            }


            private void ValidateMutationTarget(
                string capabilityName,
                JObject arguments)
            {
                DemandCurrentCompiledTypes(
                    capabilityName);

                if (_ownedArtifactPaths.Count == 0 &&
                    _mutableScenePaths.Count == 0 &&
                    MutatesUnityOrProjectState.Contains(capabilityName))
                {
                    throw new ValidationException(
                        $"[ArtifactOwnership] Kanban card cannot execute '{capabilityName}' because its committed contract declares no ProducedArtifactPaths. " +
                        "This is a read-only verification worker: inspect declared inputs, compile, run tests or Play Mode, and report evidence without mutating project or scene state.");
                }

                if (_sourceOnlyAuthority && MutatesUnityOrProjectState.Contains(capabilityName))
                {
                    string scenePath = ReadString(arguments, "ScenePath");
                    throw new ValidationException(
                        $"[ArtifactOwnership] Source-only card cannot execute '{capabilityName}': its contract authorizes source assets, not live object, scene, or project-setting mutation. " +
                        DescribeOwnershipRecovery(scenePath));
                }

                switch (capabilityName)
                {
                    case "create_editor_scene":
                        DemandMutableScenePath(
                            capabilityName,
                            ReadString(arguments, "ScenePath"));
                        return;

                    case "save_editor_scene":
                        DemandMutableScenePath(
                            capabilityName,
                            ResolveScenePath(arguments));
                        return;

                    case "create_game_object":
                        if (_mutableScenePaths.Count > 0)
                        {
                            DemandMutableScenePath(
                                capabilityName,
                                ResolveScenePath(arguments));
                        }

                        return;

                    case "instantiate_prefab":
                        if (_mutableScenePaths.Count > 0)
                        {
                            DemandMutableScenePath(
                                capabilityName,
                                ResolveScenePath(arguments));
                        }

                        return;

                    case "update_game_object":
                    case "duplicate_game_object":
                    case "destroy_game_object":
                    case "revert_or_unpack_prefab":
                    case "add_editor_component":
                    case "remove_editor_component":
                    case "set_serialized_property":
                    case "edit_serialized_collection":
                        DemandObjectBelongsToMutableScene(
                            capabilityName,
                            arguments);
                        return;

                    case "apply_prefab_overrides":
                        if (_mutableScenePaths.Count > 0)
                        {
                            throw new ValidationException(
                                "[ArtifactOwnership] A scene-scoped integration card cannot apply prefab-instance overrides to a source prefab asset. " +
                                "Mutate only the declared scene instance or route the prefab correction back to its owning card.");
                        }

                        return;

                    case "configure_project_settings":
                    case "add_unity_package":
                    case "remove_unity_package":
                        if (_mutableScenePaths.Count > 0 &&
                            _ownedArtifactPaths.Count == 0)
                        {
                            throw new ValidationException(
                                $"[ArtifactOwnership] A card holding only exact scene mutation authority cannot execute '{capabilityName}'. " +
                                "Project settings and package state require their own committed resource contract.");
                        }

                        return;

                    case "search_workspace":
                        throw new ValidationException(
                            "[ArtifactReadScope] Broad workspace content search is unavailable to a scoped Kanban child. " +
                            "Use read_file with the exact ProducedArtifactPaths and ConsumedArtifactPaths from the committed card contract. " +
                            "Unfinished sibling files are outside this worker's readable inputs.");

                    case "inspect_gameplay_readiness":
                        DemandReadablePath(capabilityName, ReadString(arguments, "ScenePath"));
                        return;

                    case "read_file":
                        DemandReadablePath(
                            capabilityName,
                            ReadString(arguments, "Path"));
                        return;

                    case "write_file":
                    case "append_file":
                    case "replace_text":
                    case "delete_file":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "Path"));
                        return;

                    case "copy_file":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "DestinationPath"));
                        return;

                    case "move_file":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "SourcePath"));
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "DestinationPath"));
                        return;

                    case "create_directory":
                        DemandOwnedDirectory(
                            capabilityName,
                            ReadString(arguments, "ParentPath"),
                            ReadString(arguments, "DirectoryName"));
                        return;

                    case "delete_directory":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "Path"));
                        return;

                    case "create_prefab":
                    case "create_material":
                    case "create_scriptable_object":
                    case "delete_unity_asset":
                    case "set_asset_importer_property":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "AssetPath"));
                        return;

                    case "copy_unity_asset":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "DestinationAssetPath"));
                        return;

                    case "move_unity_asset":
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "SourceAssetPath"));
                        DemandOwnedPath(
                            capabilityName,
                            ReadString(arguments, "DestinationAssetPath"));
                        return;

                    case "refresh_unity_assets":
                        DemandRefreshFollowsOwnedMutation(
                            arguments);
                        return;
                }
            }


            private void DemandMutableScenePath(
                string capabilityName,
                string requestedPath)
            {
                string normalized = NormalizePath(requestedPath);

                if (!string.IsNullOrWhiteSpace(normalized) &&
                    (_ownedArtifactPaths.Contains(normalized) ||
                     _mutableScenePaths.Contains(normalized)))
                {
                    return;
                }

                throw new ValidationException(
                    $"[ArtifactOwnership] Kanban card cannot execute '{capabilityName}' for scene '{requestedPath}'. " +
                    "Unity scene mutation is limited to produced .unity artifacts and exact scene:... entries in MutatedResourceKeys: " +
                    string.Join(
                        ", ",
                        _ownedArtifactPaths
                            .Where(path => path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
                            .Concat(_mutableScenePaths)
                            .Distinct(StringComparer.OrdinalIgnoreCase)
                            .OrderBy(path => path, StringComparer.OrdinalIgnoreCase)) +
                    ". ConsumedArtifactPaths are readable inputs, not mutation authority." + DescribeOwnershipRecovery(requestedPath));
            }


            private void DemandObjectBelongsToMutableScene(
                string capabilityName,
                JObject arguments)
            {
                if (_mutableScenePaths.Count == 0)
                {
                    return;
                }

                if (string.Equals(
                        capabilityName,
                        "remove_editor_component",
                        StringComparison.OrdinalIgnoreCase) &&
                    arguments?
                        .GetValue(
                            "RemoveFromAllSelected",
                            StringComparison.OrdinalIgnoreCase)?
                        .Value<bool>() == true)
                {
                    throw new ValidationException(
                        "[ArtifactOwnership] A scene-scoped integration card cannot remove components from all selected objects. Target one exact object in the declared scene.");
                }

                int instanceId =
                    arguments?
                        .GetValue(
                            "ComponentInstanceId",
                            StringComparison.OrdinalIgnoreCase)?
                        .Value<int?>() ?? 0;

                if (instanceId == 0)
                {
                    instanceId =
                        arguments?
                            .GetValue(
                                "ObjectInstanceId",
                                StringComparison.OrdinalIgnoreCase)?
                            .Value<int?>() ?? 0;
                }

                if (instanceId == 0)
                {
                    instanceId =
                        arguments?
                            .GetValue(
                                "GameObjectInstanceId",
                                StringComparison.OrdinalIgnoreCase)?
                            .Value<int?>() ?? 0;
                }

                string globalObjectId =
                    ReadString(
                        arguments,
                        "GlobalObjectId");

                if (instanceId == 0 &&
                    string.IsNullOrWhiteSpace(globalObjectId))
                {
                    throw new ValidationException(
                        $"[ArtifactOwnership] Scene-scoped capability '{capabilityName}' requires an exact GameObject, Component, or Object instance identity from current inspection evidence.");
                }

                UnityEngine.Object target =
                    EditorCapabilityUtility.ResolveObject(
                        instanceId,
                        globalObjectId,
                        false);

                GameObject gameObject =
                    target as GameObject;

                if (gameObject == null &&
                    target is Component component)
                {
                    gameObject = component.gameObject;
                }

                if (gameObject != null)
                {
                    DemandMutableScenePath(
                        capabilityName,
                        EditorCapabilityUtility.GetScenePath(gameObject));
                    return;
                }

                string assetPath =
                    UnityEditor.AssetDatabase
                        .GetAssetPath(target)
                        ?.Replace('\\', '/');

                throw new ValidationException(
                    $"[ArtifactOwnership] Scene-scoped capability '{capabilityName}' resolved to non-scene object '{target?.name}' at '{assetPath}'. " +
                    "The card may mutate only objects belonging to its exact scene:... resource keys.");
            }


            private static string ResolveScenePath(
                JObject arguments)
            {
                string destination =
                    ReadString(arguments, "DestinationScenePath");

                if (!string.IsNullOrWhiteSpace(destination))
                {
                    return destination;
                }

                string requested =
                    ReadString(arguments, "ScenePath");

                if (!string.IsNullOrWhiteSpace(requested) &&
                    requested.IndexOf('/') >= 0)
                {
                    return requested;
                }

                int sceneCount =
                    UnityEngine.SceneManagement.SceneManager.sceneCount;

                for (int index = 0; index < sceneCount; index++)
                {
                    UnityEngine.SceneManagement.Scene scene =
                        UnityEngine.SceneManagement.SceneManager.GetSceneAt(index);

                    if (!scene.IsValid() ||
                        (!string.IsNullOrWhiteSpace(requested) &&
                         !string.Equals(scene.name, requested, StringComparison.Ordinal)))
                    {
                        continue;
                    }

                    if (!string.IsNullOrWhiteSpace(scene.path))
                    {
                        return scene.path;
                    }
                }

                UnityEngine.SceneManagement.Scene active =
                    UnityEngine.SceneManagement.SceneManager.GetActiveScene();

                return active.IsValid()
                    ? active.path
                    : string.Empty;
            }


            private void DemandCurrentCompiledTypes(
                string capabilityName)
            {
                if (_pendingScriptImportPaths.Count > 0 &&
                    (string.Equals(
                         capabilityName,
                         "get_compilation_errors",
                         StringComparison.OrdinalIgnoreCase) ||
                     RequiresCurrentCompiledTypes.Contains(capabilityName)))
                {
                    throw new ValidationException(
                        "[ScriptImportRequired] Unity has not completed importing the owned source changed in this run: " +
                        string.Join(
                            ", ",
                            _pendingScriptImportPaths.OrderBy(
                                path => path,
                                StringComparer.OrdinalIgnoreCase)) +
                        ". Call refresh_unity_assets for those exact ProducedArtifactPaths, then poll get_unity_operation until it reaches Succeeded or Failed before compilation checks, component or serialized-property operations, scene saves, Play Mode, tests, or builds.");
                }

                if (_failedScriptImportPaths.Count > 0 &&
                    RequiresCurrentCompiledTypes.Contains(capabilityName))
                {
                    throw new ValidationException(
                        "[ScriptCompilationFailed] Unity attempted to import the owned source, but compilation failed: " +
                        string.Join(
                            ", ",
                            _failedScriptImportPaths.OrderBy(
                                path => path,
                                StringComparer.OrdinalIgnoreCase)) +
                        ". Call get_compilation_errors, repair the owned source, refresh it again, and wait for a Succeeded Unity operation before using loaded component types.");
                }
            }


            public bool CanReadPath(string path)
            {
                string normalized = NormalizePath(path);
                return !_enforce || (!string.IsNullOrWhiteSpace(normalized) &&
                    _readableArtifactPaths.Contains(normalized));
            }

            private void DemandReadablePath(
                string capabilityName,
                string requestedPath)
            {
                string normalized = NormalizePath(requestedPath);

                if (!string.IsNullOrWhiteSpace(normalized) &&
                    _readableArtifactPaths.Contains(normalized))
                {
                    return;
                }

                throw new ValidationException(
                    $"[ArtifactReadScope] Kanban card cannot execute '{capabilityName}' for '{requestedPath}'. " +
                    "It may read declared outputs, inputs, and completed dependency artifacts: " +
                    string.Join(", ", _readableArtifactPaths.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)) +
                    ". Do not inspect or depend on an unfinished sibling's output unless the committed graph declares it as an input.");
            }


            private void DemandRefreshFollowsOwnedMutation(
                JObject arguments)
            {
                string[] requestedPaths =
                    (arguments?
                         .GetValue(
                             "AssetPaths",
                             StringComparison.OrdinalIgnoreCase) as JArray ??
                     new JArray())
                    .Values<string>()
                    .Where(path => !string.IsNullOrWhiteSpace(path))
                    .Select(NormalizePath)
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

                if (requestedPaths.Length == 0)
                {
                    throw new ValidationException(
                        "[ArtifactOwnership] A scoped Kanban child cannot issue a workspace-wide Unity refresh. " +
                        "Pass only this card's changed ProducedArtifactPaths.");
                }

                foreach (string requestedPath in requestedPaths)
                {
                    DemandOwnedPath(
                        "refresh_unity_assets",
                        requestedPath);
                }

                string[] unchanged =
                    requestedPaths
                        .Where(path => !_mutatedArtifactPaths.Contains(path))
                        .ToArray();

                if (unchanged.Length > 0)
                {
                    throw new ValidationException(
                        "[ArtifactOwnership] refresh_unity_assets rejected unchanged artifacts in this run: " +
                        string.Join(", ", unchanged) +
                        ". Do not force-refresh on a resumed attempt. Unity already imports prior writes during assembly reload; inspect compilation and continue the card.");
                }
            }


            private void RecordSuccessfulMutation(
                string capabilityName,
                JObject arguments)
            {
                RecordSuccessfulCapability(
                    capabilityName,
                    arguments,
                    null);
            }


            private void RecordSuccessfulCapability(
                string capabilityName,
                JObject arguments,
                string resultJson)
            {
                if (capabilityName == "write_file")
                {
                    JObject writeResult = TryParseObject(resultJson);
                    string previousHash = ReadString(writeResult, "PreviousSha256");
                    if (!string.IsNullOrWhiteSpace(previousHash) && string.Equals(previousHash,
                            ReadString(writeResult, "CurrentSha256"), StringComparison.Ordinal))
                        return;
                }
                switch (capabilityName)
                {
                    case "get_compilation_errors":
                        RecordCompilationDiagnostics(resultJson);
                        return;
                    case "write_file":
                    case "append_file":
                    case "replace_text":
                        RecordPath(
                            ReadString(arguments, "Path"));
                        return;

                    case "copy_file":
                    case "move_file":
                        RecordPath(
                            ReadString(arguments, "DestinationPath"));
                        return;

                    case "create_prefab":
                    case "create_material":
                    case "create_scriptable_object":
                    case "set_asset_importer_property":
                        RecordPath(
                            ReadString(arguments, "AssetPath"));
                        return;

                    case "copy_unity_asset":
                    case "move_unity_asset":
                        RecordPath(
                            ReadString(arguments, "DestinationAssetPath"));
                        return;

                    case "refresh_unity_assets":
                        RecordScriptImportOperation(
                            arguments,
                            resultJson);
                        return;

                    case "get_unity_operation":
                        RecordScriptImportCompletion(
                            ReadString(arguments, "OperationId"),
                            resultJson);
                        return;
                }
            }


            private void RecordPath(
                string path)
            {
                string normalized = NormalizePath(path);

                if (!string.IsNullOrWhiteSpace(normalized))
                {
                    _mutatedArtifactPaths.Add(normalized);

                    if (IsScriptImportPath(normalized))
                    {
                        ResetCompilationDiagnosticProgress();
                        _pendingScriptImportPaths.Add(normalized);
                        _failedScriptImportPaths.Remove(normalized);
                    }
                }
            }


            private void RecordCompilationDiagnostics(string resultJson)
            {
                JObject result = TryParseObject(resultJson);
                if (result == null || result["IsCompiling"]?.Value<bool>() == true)
                    return;

                int? errorCount = result["ErrorCount"]?.Value<int>();
                if (!errorCount.HasValue)
                    return;
                bool diagnosticsUnavailable = result["DiagnosticsUnavailable"]?.Value<bool>() == true;
                if (errorCount.Value == 0 && !diagnosticsUnavailable)
                {
                    ResetCompilationDiagnosticProgress();
                    return;
                }
                string fingerprint = result["ErrorFingerprint"]?.Value<string>();
                if (diagnosticsUnavailable)
                    fingerprint = "DiagnosticsUnavailable:" + (fingerprint ?? string.Empty);
                if (string.IsNullOrWhiteSpace(fingerprint)) return;

                bool changedSnapshot = !string.Equals(fingerprint,
                    _lastCompilerErrorFingerprint, StringComparison.Ordinal);
                if (changedSnapshot)
                    _observedCompilerDiagnosticIdentities.Clear();

                bool exposedNewDiagnostics = false;
                foreach (JObject diagnostic in
                         (result["Messages"] as JArray ?? new JArray()).OfType<JObject>())
                {
                    if (!string.Equals(ReadString(diagnostic, "Type"), "Error",
                            StringComparison.OrdinalIgnoreCase))
                        continue;

                    string identity = new JArray(
                        ReadString(diagnostic, "AssemblyPath"),
                        ReadString(diagnostic, "File"),
                        diagnostic["Line"]?.Value<int>() ?? 0,
                        diagnostic["Column"]?.Value<int>() ?? 0,
                        ReadString(diagnostic, "Message")).ToString(Formatting.None);
                    if (_observedCompilerDiagnosticIdentities.Add(identity))
                        exposedNewDiagnostics = true;
                }

                _unchangedCompilerErrorReads = changedSnapshot || exposedNewDiagnostics
                    ? 1 : _unchangedCompilerErrorReads + 1;
                _lastCompilerErrorFingerprint = fingerprint;
                if (_unchangedCompilerErrorReads >= 3)
                    throw new CompilationNoProgressException(
                        (diagnosticsUnavailable
                            ? "[CompilationNoProgress] Unity reports compilation failure, but repeated reads have not captured its diagnostics. " +
                              "Request compilation once to collect the missing evidence, wait for it to finish, then inspect the returned errors. " +
                              "Do not infer success from an empty list or invent a failing file or compiler message. "
                            : "[CompilationNoProgress] Compiler diagnostics were read three times without a source correction or newly exposed errors. " +
                              "Inspect the failing source and completed dependency APIs, correct the owned source, then compile once. " +
                              "If the required contract is outside the approved graph, report [PlanningKickback] with the exact missing dependency. ") +
                        "Repeated polling cannot repair compilation. Last captured evidence: " +
                        BuildBoundedCompilationDiagnosticContext(result));
            }


            private void ResetCompilationDiagnosticProgress()
            {
                _unchangedCompilerErrorReads = 0;
                _lastCompilerErrorFingerprint = null;
                _observedCompilerDiagnosticIdentities.Clear();
            }


            private static string BuildBoundedCompilationDiagnosticContext(JObject result)
            {
                string context = new JObject
                {
                    ["ErrorCount"] = result["ErrorCount"]?.DeepClone(),
                    ["DiagnosticsUnavailable"] = result["DiagnosticsUnavailable"]?.Value<bool>() == true,
                    ["Messages"] = new JArray(
                        (result["Messages"] as JArray ?? new JArray())
                            .OfType<JObject>()
                            .Where(message => string.Equals(ReadString(message, "Type"),
                                "Error", StringComparison.OrdinalIgnoreCase))
                            .Take(5)
                            .Select(message => message.DeepClone()))
                }.ToString(Formatting.None);
                return context.Length <= 2400
                    ? context : context.Substring(0, 2400) + " [truncated]";
            }


            private void RecordScriptImportOperation(
                JObject arguments,
                string resultJson)
            {
                string[] scriptPaths =
                    ReadPathArray(arguments, "AssetPaths")
                        .Where(IsScriptImportPath)
                        .Where(path => _pendingScriptImportPaths.Contains(path))
                        .ToArray();

                if (scriptPaths.Length == 0 ||
                    string.IsNullOrWhiteSpace(resultJson))
                {
                    return;
                }

                JObject result = TryParseObject(resultJson);
                string operationId = ReadString(result, "OperationId");
                string state = ReadString(result, "State");

                if (!string.IsNullOrWhiteSpace(operationId))
                {
                    _scriptImportOperations[operationId] = scriptPaths;
                }

                ApplyScriptImportState(
                    operationId,
                    state,
                    scriptPaths);
            }


            private void RecordScriptImportCompletion(
                string requestedOperationId,
                string resultJson)
            {
                if (string.IsNullOrWhiteSpace(resultJson))
                {
                    return;
                }

                JObject result = TryParseObject(resultJson);
                string operationId = ReadString(result, "OperationId");

                if (string.IsNullOrWhiteSpace(operationId))
                {
                    operationId = requestedOperationId;
                }

                if (string.IsNullOrWhiteSpace(operationId) ||
                    !_scriptImportOperations.TryGetValue(
                        operationId,
                        out string[] scriptPaths))
                {
                    return;
                }

                ApplyScriptImportState(
                    operationId,
                    ReadString(result, "State"),
                    scriptPaths);
            }


            private void ApplyScriptImportState(
                string operationId,
                string state,
                IEnumerable<string> scriptPaths)
            {
                bool succeeded = string.Equals(
                    state,
                    "Succeeded",
                    StringComparison.OrdinalIgnoreCase);
                bool failed = string.Equals(
                    state,
                    "Failed",
                    StringComparison.OrdinalIgnoreCase);

                if (!succeeded && !failed)
                {
                    return;
                }

                foreach (string path in scriptPaths ??
                         Enumerable.Empty<string>())
                {
                    _pendingScriptImportPaths.Remove(path);

                    if (failed)
                    {
                        _failedScriptImportPaths.Add(path);
                    }
                    else
                    {
                        _failedScriptImportPaths.Remove(path);
                    }
                }

                if (!string.IsNullOrWhiteSpace(operationId))
                {
                    _scriptImportOperations.Remove(operationId);
                }
            }


            private static bool IsScriptImportPath(
                string path)
            {
                string extension = Path.GetExtension(path ?? string.Empty);

                return string.Equals(
                           extension,
                           ".cs",
                           StringComparison.OrdinalIgnoreCase) ||
                       string.Equals(
                           extension,
                           ".asmdef",
                           StringComparison.OrdinalIgnoreCase) ||
                       string.Equals(
                           extension,
                           ".asmref",
                           StringComparison.OrdinalIgnoreCase);
            }


            private static string[] ReadPathArray(
                JObject arguments,
                string propertyName)
            {
                return (arguments?
                            .GetValue(
                                propertyName,
                                StringComparison.OrdinalIgnoreCase) as JArray ??
                        new JArray())
                    .Values<string>()
                    .Where(path => !string.IsNullOrWhiteSpace(path))
                    .Select(NormalizePath)
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }


            private static JObject TryParseObject(
                string json)
            {
                try
                {
                    return string.IsNullOrWhiteSpace(json)
                        ? new JObject()
                        : JObject.Parse(json);
                }
                catch (JsonException)
                {
                    return new JObject();
                }
            }


            private void DemandOwnedPath(
                string capabilityName,
                string requestedPath)
            {
                string normalized = NormalizePath(requestedPath);

                if (!string.IsNullOrWhiteSpace(normalized) &&
                    _ownedArtifactPaths.Contains(normalized))
                {
                    return;
                }

                throw new ValidationException(
                    $"[ArtifactOwnership] Kanban card cannot execute '{capabilityName}' for '{requestedPath}'. " +
                    "It may mutate only its declared ProducedArtifactPaths: " +
                    string.Join(", ", _ownedArtifactPaths.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)) +
                    ". Do not create or overwrite a sibling card's output." + DescribeOwnershipRecovery(requestedPath));
            }


            public bool CanCreateDirectory(string path)
            {
                if (!_enforce) return true;
                string normalized = NormalizePath(path);
                string prefix = normalized.TrimEnd('/') + "/";
                return !string.IsNullOrWhiteSpace(normalized) &&
                    (_ownedArtifactPaths.Contains(normalized) ||
                     _ownedArtifactPaths.Any(owned => owned.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)));
            }

            private void DemandOwnedDirectory(
                string capabilityName,
                string parentPath,
                string directoryName)
            {
                string combined = NormalizePath(
                    string.IsNullOrWhiteSpace(parentPath) ||
                    string.Equals(parentPath, ".", StringComparison.Ordinal)
                        ? directoryName
                        : parentPath.TrimEnd('/', '\\') + "/" + directoryName);

                string prefix = combined.TrimEnd('/') + "/";

                if (!string.IsNullOrWhiteSpace(combined) &&
                    (_ownedArtifactPaths.Contains(combined) ||
                     _ownedArtifactPaths.Any(path =>
                         path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))))
                {
                    return;
                }

                DemandOwnedPath(
                    capabilityName,
                    combined);
            }


            private static string ReadString(
                JObject arguments,
                string name)
            {
                return arguments?
                    .GetValue(name, StringComparison.OrdinalIgnoreCase)?
                    .Value<string>() ?? string.Empty;
            }


            private static string NormalizePath(
                string path)
            {
                string normalized =
                    (path ?? string.Empty)
                    .Trim()
                    .Replace('\\', '/');

                while (normalized.StartsWith("./", StringComparison.Ordinal))
                {
                    normalized = normalized.Substring(2);
                }

                return normalized.TrimEnd('/');
            }
        }


        private sealed class SerializedBooleanVerification
        {
            public int ComponentInstanceId;
            public string PropertyPath;
            public bool InitialValue;
            public string ToggleArguments;
            public string RestoreArguments;
        }


        private sealed class CopyVerificationPaths
        {
            public string SourcePath;
            public string DestinationPath;
        }


        private sealed class ToolResultCaptureObserver :
            IAgentObserver
        {
            private readonly string _toolName;


            public ToolResultCaptureObserver(
                string toolName)
            {
                _toolName =
                    toolName;
            }


            public string Result
            {
                get;
                private set;
            }

            public int CompletedCount
            {
                get;
                private set;
            }

            public List<string> Results { get; } =
                new List<string>();


            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                if (agentEvent !=
                        null &&
                    agentEvent.Type ==
                    AgentEventType.ToolCompleted &&
                    string.Equals(
                        agentEvent.ToolName,
                        _toolName,
                        StringComparison.Ordinal))
                {
                    Result =
                        agentEvent.Message;

                    Results.Add(
                        agentEvent.Message);

                    CompletedCount++;
                }
            }
        }


        [Serializable]
        private sealed class ExecutionMemoryDocument
        {
            public int Version = 1;
            public string UpdatedAt;
            public List<ExecutionMemoryEntry> Entries =
                new List<ExecutionMemoryEntry>();
        }


        [Serializable]
        private sealed class ExecutionMemoryEntry
        {
            public string Timestamp;
            public string Event;
            public string Tool;
            public string Arguments;
            public string Result;
            public int RepeatCount = 1;
        }


        private sealed class ExecutionMemoryObserver :
            IAgentObserver
        {
            private const int MaxEntries = 48;
            private const int MaxSerializedCharacters = 24000;
            private const int MaxArgumentCharacters = 900;
            private const int MaxResultCharacters = 1400;

            private readonly object _gate = new object();
            private readonly Action<string> _sink;
            private readonly Action<AgentEvent> _historySink;
            private readonly ExecutionMemoryDocument _document;


            public ExecutionMemoryObserver(string existingMemory, Action<string> sink)
                : this(existingMemory, sink, null) { }

            public ExecutionMemoryObserver(
                string existingMemory,
                Action<string> sink,
                Action<AgentEvent> historySink)
            {
                _sink = sink;
                _historySink = historySink;
                _document = ParseExecutionMemory(existingMemory);
            }


            public void OnAgentEvent(
                AgentEvent agentEvent)
            {
                if (agentEvent == null ||
                    _sink == null)
                {
                    return;
                }

                lock (_gate)
                {
                    _historySink?.Invoke(agentEvent);
                    switch (agentEvent.Type)
                    {
                        case AgentEventType.RunStarted:
                            AddEntry(
                                new ExecutionMemoryEntry
                                {
                                    Timestamp = agentEvent.Timestamp.ToString("O"),
                                    Event = "RunStarted"
                                });
                            break;

                        case AgentEventType.ToolStarted:
                            AddEntry(
                                new ExecutionMemoryEntry
                                {
                                    Timestamp = agentEvent.Timestamp.ToString("O"),
                                    Event = "ToolStarted",
                                    Tool = agentEvent.ToolName ?? string.Empty,
                                    Arguments = BoundMemoryValue(
                                        agentEvent.Message,
                                        MaxArgumentCharacters)
                                });
                            break;

                        case AgentEventType.ToolCompleted:
                        case AgentEventType.ToolFailed:
                            CompleteLatestToolEntry(agentEvent);
                            break;

                        case AgentEventType.ModelResponseRepair:
                        case AgentEventType.ModelResponseNormalized:
                            AddEntry(
                                new ExecutionMemoryEntry
                                {
                                    Timestamp = agentEvent.Timestamp.ToString("O"),
                                    Event = agentEvent.Type.ToString(),
                                    Result = BoundMemoryValue(
                                        agentEvent.Message,
                                        MaxResultCharacters)
                                });
                            break;

                        case AgentEventType.RunCompleted:
                        case AgentEventType.RunCancelled:
                        case AgentEventType.RunTimedOut:
                        case AgentEventType.RunFailed:
                            AddEntry(
                                new ExecutionMemoryEntry
                                {
                                    Timestamp = agentEvent.Timestamp.ToString("O"),
                                    Event = agentEvent.Type.ToString(),
                                    Result = BoundMemoryValue(
                                        agentEvent.Message,
                                        MaxResultCharacters)
                                });
                            break;
                    }

                    Publish();
                }
            }


            private void CompleteLatestToolEntry(
                AgentEvent agentEvent)
            {
                ExecutionMemoryEntry entry =
                    _document.Entries.LastOrDefault(
                        candidate =>
                            string.Equals(
                                candidate.Event,
                                "ToolStarted",
                                StringComparison.Ordinal) &&
                            string.Equals(
                                candidate.Tool,
                                agentEvent.ToolName,
                                StringComparison.Ordinal));

                if (entry == null)
                {
                    entry = new ExecutionMemoryEntry
                    {
                        Timestamp = agentEvent.Timestamp.ToString("O"),
                        Tool = agentEvent.ToolName ?? string.Empty
                    };
                    AddEntry(entry);
                }

                entry.Event =
                    agentEvent.Type == AgentEventType.ToolCompleted
                        ? "ToolCompleted"
                        : "ToolFailed";
                entry.Timestamp = agentEvent.Timestamp.ToString("O");
                entry.Result = BoundMemoryValue(
                    agentEvent.Message,
                    MaxResultCharacters);

                int index = _document.Entries.IndexOf(entry);
                if (index > 0)
                {
                    ExecutionMemoryEntry previous =
                        _document.Entries[index - 1];

                    if (string.Equals(previous.Event, entry.Event, StringComparison.Ordinal) &&
                        string.Equals(previous.Tool, entry.Tool, StringComparison.Ordinal) &&
                        string.Equals(previous.Arguments, entry.Arguments, StringComparison.Ordinal) &&
                        string.Equals(previous.Result, entry.Result, StringComparison.Ordinal))
                    {
                        previous.Timestamp = entry.Timestamp;
                        previous.RepeatCount = Math.Max(1, previous.RepeatCount) + 1;
                        _document.Entries.RemoveAt(index);
                    }
                }
            }


            private void AddEntry(
                ExecutionMemoryEntry entry)
            {
                _document.Entries.Add(entry);
            }


            private void Publish()
            {
                _document.UpdatedAt = DateTime.UtcNow.ToString("O");

                while (_document.Entries.Count > MaxEntries)
                {
                    _document.Entries.RemoveAt(0);
                }

                string serialized =
                    JsonConvert.SerializeObject(
                        _document,
                        Formatting.Indented);

                while (serialized.Length > MaxSerializedCharacters &&
                       _document.Entries.Count > 1)
                {
                    _document.Entries.RemoveAt(0);
                    serialized = JsonConvert.SerializeObject(
                        _document,
                        Formatting.Indented);
                }

                try
                {
                    _sink(serialized);
                }
                catch
                {
                    // Journaling must never fail the assigned task.
                }
            }
        }


        private static ExecutionMemoryDocument ParseExecutionMemory(
            string value)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                try
                {
                    ExecutionMemoryDocument parsed =
                        JsonConvert.DeserializeObject<ExecutionMemoryDocument>(
                            value);

                    if (parsed != null)
                    {
                        parsed.Entries =
                            parsed.Entries ??
                            new List<ExecutionMemoryEntry>();
                        return parsed;
                    }
                }
                catch
                {
                    // Preserve legacy/free-form memory as one bounded entry.
                }
            }

            ExecutionMemoryDocument document =
                new ExecutionMemoryDocument();

            if (!string.IsNullOrWhiteSpace(value))
            {
                document.Entries.Add(
                    new ExecutionMemoryEntry
                    {
                        Timestamp = DateTime.UtcNow.ToString("O"),
                        Event = "PriorMemory",
                        Result = BoundMemoryValue(value, 1400)
                    });
            }

            return document;
        }


        private static string BoundMemoryValue(
            string value,
            int maximumCharacters)
        {
            string normalized =
                value ??
                string.Empty;

            if (normalized.Length <= maximumCharacters)
            {
                return normalized;
            }

            return normalized.Substring(0, maximumCharacters) +
                   $"\n...[execution memory bounded; original characters={normalized.Length}]";
        }


        private static string AppendExecutionMemory(
            string prompt,
            string executionMemory)
        {
            if (string.IsNullOrWhiteSpace(executionMemory))
            {
                return prompt;
            }

            return (prompt ?? string.Empty).TrimEnd() +
                   "\n\nPERSISTED CARD EXECUTION MEMORY:\n" +
                   "This bounded journal contains prior runs and tool observations for this exact card. Continue from successful work already recorded. Do not repeat an identical successful read merely to rediscover the same state; re-read only when the state is volatile or a later mutation could have changed it. Tool results are bounded summaries, so inspect a narrower target when omitted detail is genuinely required.\n" +
                   executionMemory.Trim();
        }


        private static string AppendExecutionMemoryForRun(
            string prompt,
            string executionMemory,
            bool reviewMode,
            bool coordinationOnly)
        {
            if (reviewMode || coordinationOnly)
                return prompt ?? string.Empty;

            return AppendExecutionMemory(
                prompt,
                executionMemory);
        }


        private const string TaskSystemPrompt =
            "You are executing an assigned Kanban task. Work only inside the configured " +
            "workspace. Use the available tools when needed, complete and verify the work, " +
            "then return a concise completion report. Do not claim that work was completed " +
            "unless the required changes and verification actually succeeded.";

        private const string RootFileListSystemPrompt =
            "For a task asking for all files in the root or top-level directory, you must call " +
            "list_directory with path=\".\", entryType=\"Files\", and outputFormat=\"PathsOnly\". Your entire final response " +
            "must then be the complete set of returned Path values, copied exactly, one path per " +
            "line. Do not analyze the files. Do not mention counts, sizes, extensions, categories, " +
            "optimizations, recommendations, or conclusions. Do not add a heading, bullets, " +
            "numbering, markdown, or an offer of more help. Metadata in the tool result is input " +
            "data only and must never appear in the final response.";

        private const string FileContentSystemPrompt =
            "When the current active request asks what a named file says, contains, documents, " +
            "defines, or asks for a summary of that file, you must call read_file before answering. " +
            "A directory listing, filename, earlier response, or acknowledgement is not file-content " +
            "evidence. Base the answer only on the contents returned by read_file.";

        private const string ComponentInspectionSystemPrompt =
            "When the current active request asks to inspect, troubleshoot, diagnose, or fix a " +
            "selected Unity component, you must call get_editor_selection and then " +
            "inspect_editor_component before answering. Base the diagnosis on returned properties " +
            "and Diagnostics. The inspection tool is read-only. If the user requested a supported " +
            "property fix, call get_serialized_properties, then set_serialized_property with the " +
            "exact path and expected current value, and report success only when Verified=true. " +
            "Never invent an object reference or choose an arbitrary asset. When a fix requires a " +
            "mesh or other asset that is not authoritatively available, explain why it cannot be " +
            "fixed safely and provide concrete manual options; that satisfies a request which says " +
            "to fix it if possible, otherwise provide options.";

        private const string SceneAuthoringSystemPrompt =
            "When a task asks you to create or build a Unity scene, call create_editor_scene " +
            "before inspecting that scene. Create each scene object with create_game_object; use its " +
            "ComponentTypes input when the object must be constructed with a special Transform subtype such " +
            "as RectTransform. Use update_game_object for identity, hierarchy, activation, tag, layer, order, " +
            "and static flags, and instantiate_prefab for prefab instances. Then attach and configure " +
            "components with the component and serialized-property " +
            "tools. Before add_editor_component, inspect the GameObject and reuse an existing exact-type " +
            "component when present; never add a duplicate merely because the task is being reworked or " +
            "reviewed. Never remove a component or missing-script slot unless the active card explicitly " +
            "requests that removal; an inspection observation is not cleanup authorization. When multiple " +
            "same-type components legitimately exist, pass the intended exact " +
            "ComponentInstanceId from inspection to all serialized-property calls. Never pass an invented " +
            "or not-yet-created assetPath to set_serialized_property. " +
            "When a new Material is required, call create_material first and assign its exact returned " +
            "AssetPath only after Succeeded=true. A custom material is optional unless the task asks " +
            "for one; Unity primitive objects already have a usable default material. When a prefab " +
            "is required, author and inspect its source GameObject in a persisted scene, then call " +
            "create_prefab with the exact live source identity and destination AssetPath. Verify the " +
            "returned asset with inspect_prefab. Never synthesize or rename scene YAML as a .prefab. " +
            "Use configure_project_settings for tags, layers, sorting layers, and build-scene membership; do not " +
            "edit ProjectSettings YAML. Inspect and change import settings through inspect_asset_importer and " +
            "set_asset_importer_property. Use find_unity_assets and inspect_unity_asset for Unity asset identity " +
            "and dependencies. Move, copy, or delete Unity assets only through the corresponding unity_asset " +
            "capability so GUIDs and .meta files remain coherent; use filesystem mutation only for ordinary " +
            "non-asset files. After script, asmdef, importer, or package changes, call refresh_unity_assets and " +
            "poll get_unity_operation until the durable import/compilation operation succeeds across any reload. " +
            "Use edit_serialized_collection for arrays and lists rather than attempting raw serialized blobs. " +
            "Use control_play_mode, wait_for_editor_frames, camera capture, tests, " +
            "and build_player when the acceptance criteria require runtime, visual, test, or player evidence. " +
            "A GameObject named Ground is not automatically on a layer named Ground. Use the name filter to find " +
            "named objects, and use layerName only for a Unity layer known to exist. Unless the task " +
            "explicitly establishes a custom layer, keep authored objects on the Default layer. For " +
            "serialized object references, use set_serialized_property's structured ObjectReference " +
            "argument and copy AssetPath, or the complete InstanceId and GlobalObjectId, from an " +
            "authoritative creation or inspection result. Never JSON-encode an object reference inside " +
            "ValueJson, and never extract, invent, or reuse only its globalObjectId. An " +
            "integer Inspector property backed by a flagged enum must be written with semantic names, " +
            "for example ValueJson={\"flags\":[\"FreezePositionZ\"]}; never guess or calculate a numeric " +
            "bitmask. If the capability rejects a numeric value and returns valid flag names, retry once " +
            "with those exact names. " +
            "empty globalObjectId means the object is not persistently addressable. Built-in default " +
            "materials do not need to be reassigned. Call " +
            "save_editor_scene after mutations and inspect_scene to verify. Never " +
            "write or synthesize .unity YAML with write_file, and never inspect a requested " +
            "scene path before its creation tool succeeds. Any GameObject created or configured " +
            "as a task deliverable must belong to a scene with a non-empty Assets/.../.unity asset " +
            "path. Never leave the deliverable only in an untitled scene. If the active scene is " +
            "untitled and already contains the card's intended hierarchy, preserve it by calling " +
            "save_editor_scene with destinationScenePath set to the exact unused .unity path declared " +
            "by the card; this persists it without a Save As dialog. If that untitled hierarchy is not " +
            "the card's intended work, resolve and open the intended persisted scene or create it first. " +
            "After AIRuntime replays an unchanged read-only capability result, do not request the same " +
            "inspection again: perform the required mutation, choose a materially different observation, " +
            "request a missing capability, or finish. The task " +
            "is not complete unless save_editor_scene succeeds and a following inspect_scene result " +
            "reports Dirty=false; a save failure is in scope and must never be dismissed.";

        private const string DirectoryScaffoldingSystemPrompt =
            "When a task asks you to create a new project folder structure, do not assume the " +
            "new root folder already exists. First call create_directory for the root itself " +
            "(for example parentPath=\"Assets\", directoryName=\"ExampleProject\", " +
            "createParents=true). Wait for that call to succeed, then create each requested " +
            "child beneath the returned Path. Alternatively, create each complete requested " +
            "path with createParents=true. Never begin by using a not-yet-created root as " +
            "parentPath. This scaffolding rule does not change add-NAME-to-existing-PARENT " +
            "requests: those must still use createParents=false and fail when PARENT is absent.";


        internal static async Task<string> RunAsync(
            string agentId,
            string title,
            string description,
            string acceptanceCriteria,
            string previousAgentOutput,
            string nextAgentMessage,
            bool reviewMode,
            bool coordinationOnly,
            CancellationToken cancellationToken,
            Action<string> reviewEvidenceSink = null,
            string executionMemory = null,
            Action<string> executionMemorySink = null,
            string correlationId = null,
            IServiceProvider capabilityServices = null,
            Action<AgentEvent> historySink = null,
            string cardMemoryContext = null,
            Action<string> humanVisualEvidenceSink = null)
        {
            AIRuntimeEditorAgentConfiguration configuration =
                LoadConfiguration(
                    agentId);

            ValidateConfiguration(
                configuration);

            string originalCardRequest =
                string.Join(
                    "\n",
                    title ?? string.Empty,
                    description ?? string.Empty,
                    acceptanceCriteria ?? string.Empty);

            originalCardRequest =
                NormalizeExistingSceneReferences(
                    originalCardRequest);

            string effectiveRequest =
                reviewMode
                    ? GetReviewEffectiveRequest(
                        title,
                        description,
                        acceptanceCriteria,
                        nextAgentMessage)
                    : GetEffectiveRequest(
                        title,
                        description,
                        acceptanceCriteria,
                        nextAgentMessage);

            effectiveRequest =
                NormalizeExistingSceneReferences(
                    effectiveRequest);

            string verifiedCapability =
                ExtractVerifiedCapabilityName(
                    effectiveRequest);

            bool verifiesAppendFile =
                string.Equals(
                    verifiedCapability,
                    "append_file",
                    StringComparison.Ordinal);

            bool verifiesCopyFile =
                string.Equals(
                    verifiedCapability,
                    "copy_file",
                    StringComparison.Ordinal);

            bool verifiesSetSerializedProperty =
                string.Equals(
                    verifiedCapability,
                    "set_serialized_property",
                    StringComparison.Ordinal);

            // Resolve the same durable ownership contract used by capability
            // enforcement before choosing any preflights or system instructions.
            var scopedServices = capabilityServices?.GetService(typeof(IKanbanTaskExecutionServices))
                as IKanbanTaskExecutionServices;
            string initialTaskContext = scopedServices == null ? null :
                JsonConvert.SerializeObject(scopedServices.ReadTaskContext());
            bool sourceArtifactScope = !coordinationOnly && IsSourceArtifactContext(initialTaskContext);

            string classificationRequest =
                GetClassificationRequest(
                    originalCardRequest,
                    effectiveRequest,
                    nextAgentMessage);

            string evidenceTargetRequest =
                classificationRequest;

            bool requiresEditorSelection =
                RequiresEditorSelection(
                     classificationRequest) ||
                 verifiesSetSerializedProperty;

            bool requiresRootFileList =
                RequiresRootFileList(
                    classificationRequest);

            bool requiresDirectoryTree =
                RequiresDirectoryTree(
                    classificationRequest);

            bool requiresFileContentRead =
                ((reviewMode
                     ? RequiresFileContentRead(
                         classificationRequest)
                     : RequiresImplementationFileContentRead(
                         classificationRequest)) ||
                 (reviewMode &&
                   RequiresReviewFileEvidence(
                       classificationRequest))) &&
                !verifiesAppendFile &&
                !verifiesCopyFile;

            bool hasExplicitReviewFilePaths =
                reviewMode &&
                requiresFileContentRead &&
                Regex.IsMatch(
                    classificationRequest,
                    @"Assets(?:[\/][A-Za-z0-9_.-]+)+\.(?:cs|json|md|txt)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool standaloneMaterialAssetReview =
                IsStandaloneMaterialAssetReview(
                    reviewMode,
                    classificationRequest);

            bool standaloneScriptAssetReview =
                IsStandaloneScriptAssetReview(
                    reviewMode,
                    classificationRequest);

            bool standaloneDirectoryScaffolding =
                IsStandaloneDirectoryScaffoldingTask(
                    classificationRequest);

            bool requiresComponentInspection =
                !standaloneMaterialAssetReview &&
                !standaloneDirectoryScaffolding &&
                !standaloneScriptAssetReview &&
                RequiresRunComponentInspection(
                    reviewMode,
                    classificationRequest,
                    effectiveRequest,
                    verifiesSetSerializedProperty);

            bool requestsComponentFix =
                requiresComponentInspection &&
                RequestsComponentFix(
                    classificationRequest);

            bool requiresComponentRemoval =
                !reviewMode &&
                (RequiresComponentRemovalVerification(
                     classificationRequest) ||
                 RequestsAllSelectedMissingScriptRemoval(
                     classificationRequest));

            bool requiresHierarchyTree =
                RequiresHierarchyTree(
                    classificationRequest);

            bool requiresSelectedHierarchyTree =
                RequiresSelectedHierarchyTree(
                    classificationRequest);

            bool requiresCompilationCheck =
                classificationRequest.IndexOf(
                     "get_compilation_errors",
                     StringComparison.OrdinalIgnoreCase) >=
                     0 ||
                 (reviewMode &&
                   RequiresReviewCompilationEvidence(
                      classificationRequest));

            bool requiresMaterialAssetEvidence =
                reviewMode &&
                RequiresMaterialAssetReviewEvidence(
                    classificationRequest);

            bool requiresSceneAuthoring =
                !reviewMode &&
                RequiresSceneAuthoring(
                    classificationRequest);

            bool requiresSceneInspection =
                !standaloneMaterialAssetReview &&
                !standaloneDirectoryScaffolding &&
                RequiresSceneInspectionEvidence(
                    reviewMode,
                    classificationRequest);

            bool requiresDirectoryScaffolding =
                !reviewMode &&
                RequiresDirectoryScaffolding(
                    classificationRequest);

            IReadOnlyList<string> requiredExistingDirectories =
                ExtractRequiredExistingDirectories(
                    classificationRequest);

            bool requiresDirectoryExistenceCheck =
                requiredExistingDirectories.Count > 0;

            string expectedCreatedGameObject =
                !reviewMode
                    ? ExtractExpectedCreatedGameObjectName(
                        classificationRequest)
                    : null;

            bool requiresPersistedSceneObject =
                !string.IsNullOrWhiteSpace(
                    expectedCreatedGameObject) &&
                string.IsNullOrWhiteSpace(
                    verifiedCapability);

            requiresSceneAuthoring =
                requiresSceneAuthoring ||
                requiresPersistedSceneObject;

            if (sourceArtifactScope)
            {
                standaloneScriptAssetReview = reviewMode;
                requiresFileContentRead = true;
                requiresCompilationCheck = true;
                requiresComponentInspection = false;
                requestsComponentFix = false;
                requiresComponentRemoval = false;
                requiresEditorSelection = false;
                requiresHierarchyTree = false;
                requiresSelectedHierarchyTree = false;
                requiresSceneAuthoring = false;
                requiresSceneInspection = false;
                requiresPersistedSceneObject = false;
                expectedCreatedGameObject = null;
                requiresDirectoryScaffolding = false;
            }

            if (coordinationOnly)
            {
                // A Hermes-style orchestrator never touches implementation
                // state. Suppress all task-specific preflights and later
                // expose only the two scoped Kanban graph capabilities.
                verifiedCapability = null;
                verifiesAppendFile = false;
                verifiesCopyFile = false;
                verifiesSetSerializedProperty = false;
                requiresEditorSelection = false;
                requiresRootFileList = false;
                requiresDirectoryTree = false;
                requiresFileContentRead = false;
                requiresComponentInspection = false;
                requestsComponentFix = false;
                requiresComponentRemoval = false;
                requiresHierarchyTree = false;
                requiresSelectedHierarchyTree = false;
                requiresCompilationCheck = false;
                requiresMaterialAssetEvidence = false;
                requiresSceneAuthoring = false;
                requiresSceneInspection = false;
                requiresDirectoryScaffolding = false;
                requiredExistingDirectories =
                    Array.Empty<string>();
                requiresDirectoryExistenceCheck = false;
                expectedCreatedGameObject = null;
                requiresPersistedSceneObject = false;
            }

            configuration.EnableTools = true;
            configuration.EnabledTools =
                configuration.EnabledTools ??
                new List<string>();

            AddCapabilityIfMissing(
                configuration.EnabledTools,
                "get_kanban_task_context");

            if (coordinationOnly)
            {
                AddCapabilityIfMissing(
                    configuration.EnabledTools,
                    "create_kanban_child_task");
            }

            ConfigureComponentRemovalCapability(
                configuration.EnabledTools,
                requiresComponentRemoval ||
                string.Equals(
                    verifiedCapability,
                    "remove_editor_component",
                    StringComparison.Ordinal));

            if (!string.IsNullOrWhiteSpace(
                    verifiedCapability) ||
                requiresRootFileList ||
                requiresDirectoryTree ||
                requiresFileContentRead ||
                requiresComponentInspection ||
                requiresHierarchyTree ||
                 requiresCompilationCheck ||
                 requiresMaterialAssetEvidence ||
                 requiresDirectoryExistenceCheck ||
                requiresSceneAuthoring ||
                requiresSceneInspection ||
                requiresDirectoryScaffolding ||
                !string.IsNullOrWhiteSpace(expectedCreatedGameObject))
            {
                configuration.EnableTools =
                    true;

                configuration.EnabledTools =
                    configuration.EnabledTools ??
                    new List<string>();

                if (!string.IsNullOrWhiteSpace(
                        verifiedCapability))
                {
                    // Capability verification cards are executable tests.
                    // Their named tool must be available even when the
                    // agent's saved default tool subset omits it.
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        verifiedCapability);
                }

                if (verifiesAppendFile)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "read_file");

                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "write_file");
                }

                if (verifiesCopyFile)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "read_file");
                }

                if (requiresRootFileList)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "list_directory");
                }

                if (requiresDirectoryTree)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "list_directory_tree");
                }

                if (requiresFileContentRead)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "read_file");
                }

                if (requiresCompilationCheck)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "get_compilation_errors");
                }

                if (requiresMaterialAssetEvidence)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "get_serialized_properties");
                }

                if (requiresDirectoryExistenceCheck)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "get_file_info");

                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "list_directory");
                }

                if (requiresComponentInspection)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "inspect_editor_component");

                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "find_game_objects");

                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "get_serialized_properties");

                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "set_serialized_property");

                    if (reviewMode)
                    {
                        AddCapabilityIfMissing(
                            configuration.EnabledTools,
                            "inspect_scene");

                        AddCapabilityIfMissing(
                            configuration.EnabledTools,
                            "read_file");
                    }

                    if (requestsComponentFix)
                    {
                        AddCapabilityIfMissing(
                            configuration.EnabledTools,
                            "add_editor_component");
                    }

                    if (requiresComponentRemoval)
                    {
                        AddCapabilityIfMissing(
                            configuration.EnabledTools,
                            "remove_editor_component");
                    }
                }

                if (requiresHierarchyTree)
                {
                    AddCapabilityIfMissing(
                        configuration.EnabledTools,
                        "get_hierarchy_tree");
                }

                if (requiresSceneAuthoring)
                {
                    AddCapabilityIfMissing(configuration.EnabledTools, "create_editor_scene");
                    AddCapabilityIfMissing(configuration.EnabledTools, "create_game_object");
                    AddCapabilityIfMissing(configuration.EnabledTools, "save_editor_scene");
                    AddCapabilityIfMissing(configuration.EnabledTools, "inspect_scene");
                    AddCapabilityIfMissing(configuration.EnabledTools, "inspect_game_object");
                    AddCapabilityIfMissing(configuration.EnabledTools, "add_editor_component");
                    AddCapabilityIfMissing(configuration.EnabledTools, "get_serialized_properties");
                    AddCapabilityIfMissing(configuration.EnabledTools, "set_serialized_property");
                    AddCapabilityIfMissing(configuration.EnabledTools, "create_material");
                }

                if (requiresSceneInspection)
                {
                    AddCapabilityIfMissing(configuration.EnabledTools, "inspect_scene");
                }

                if (requiresDirectoryScaffolding)
                {
                    AddCapabilityIfMissing(configuration.EnabledTools, "create_directory");
                }

                if (!string.IsNullOrWhiteSpace(expectedCreatedGameObject))
                {
                    AddCapabilityIfMissing(configuration.EnabledTools, "find_game_objects");
                }
            }
            else if (requiresEditorSelection &&
                     !configuration.EnableTools)
            {
                // Selection is live editor state, so it cannot be answered
                // reliably without the dedicated read-only capability. Enable
                // only that capability for this run; do not alter the saved
                // agent configuration or expose unrelated tools.
                configuration.EnableTools =
                    true;

                configuration.EnabledTools =
                    new List<string>
                    {
                        "get_editor_selection"
                    };
            }

            if (coordinationOnly)
            {
                configuration.EnableTools =
                    true;

                configuration.EnabledTools =
                    new List<string>
                    {
                        "get_kanban_task_context",
                        "create_kanban_child_task",
                        "commit_kanban_plan",
                        "save_kanban_checkpoint",
                        "read_kanban_memory"
                    };
            }
            else
            {
                // Kanban agents are execution workers, not chat presets. Give
                // them the complete installed Kanban capability surface so
                // strategy is never constrained by a stale per-agent tool
                // checklist. Permission policy still independently governs
                // ReadOnly, Write, and Destructive execution.
                configuration.EnableTools = true;
                configuration.EnabledTools =
                    ResolveKanbanCapabilitiesForRun(
                        coordinationOnly: false)
                    .ToList();
            }

            KanbanArtifactOwnershipMiddleware artifactOwnership =
                new KanbanArtifactOwnershipMiddleware();

            var humanVisualReview = new KanbanHumanVisualReviewMiddleware { EvidenceSink = humanVisualEvidenceSink };

            RuntimeFacade runtime =
                new global::AIRuntime.Runtime.AIRuntimeBuilder(
                        configuration.WorkspaceRoot.Trim())
                    .UseMiddleware(artifactOwnership)
                    .UseMiddleware(humanVisualReview)
                    .Build();

            // A Kanban card is only one consumer of a shared provider lane.
            // Let the durable board scheduler pause that lane on HTTP 429
            // instead of spending a second request inside a rate-limit window.
            runtime.SetProviderFailureRetryable(
                ProviderFailureKind.RateLimited,
                retryable: false);

            ExecutionMemoryObserver executionMemoryObserver =
                executionMemorySink == null
                    ? null
                    : new ExecutionMemoryObserver(
                        coordinationOnly
                            ? string.Empty
                            : executionMemory,
                        executionMemorySink, historySink);

            if (executionMemoryObserver != null)
            {
                runtime.AddAgentObserver(
                    executionMemoryObserver);
            }

            runtime.RunTimeout =
                ResolveKanbanRunTimeout();

            ToolResultCaptureObserver rootListCapture =
                requiresRootFileList
                    ? new ToolResultCaptureObserver(
                        "list_directory")
                    : null;

            ToolResultCaptureObserver fileReadCapture =
                requiresFileContentRead
                    ? new ToolResultCaptureObserver(
                        "read_file")
                    : null;

            ToolResultCaptureObserver directoryTreeCapture =
                requiresDirectoryTree
                    ? new ToolResultCaptureObserver(
                        "list_directory_tree")
                    : null;

            ToolResultCaptureObserver componentInspectionCapture =
                requiresComponentInspection
                    ? new ToolResultCaptureObserver(
                        "inspect_editor_component")
                    : null;

            ToolResultCaptureObserver componentRemovalCapture =
                requiresComponentRemoval
                    ? new ToolResultCaptureObserver(
                        "remove_editor_component")
                    : null;

            ToolResultCaptureObserver serializedPropertyMutationCapture =
                verifiesSetSerializedProperty
                    ? new ToolResultCaptureObserver(
                        "set_serialized_property")
                    : null;

            ToolResultCaptureObserver copyFileCapture =
                verifiesCopyFile &&
                !reviewMode
                    ? new ToolResultCaptureObserver(
                        "copy_file")
                    : null;

            ToolResultCaptureObserver appendFileCapture =
                verifiesAppendFile &&
                !reviewMode
                    ? new ToolResultCaptureObserver(
                        "append_file")
                    : null;

            ToolResultCaptureObserver sceneSaveCapture =
                requiresPersistedSceneObject
                    ? new ToolResultCaptureObserver(
                        "save_editor_scene")
                    : null;

            ToolResultCaptureObserver sceneInspectionCapture =
                requiresPersistedSceneObject
                    ? new ToolResultCaptureObserver(
                        "inspect_scene")
                    : null;

            if (rootListCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    rootListCapture);
            }

            if (fileReadCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    fileReadCapture);
            }

            if (directoryTreeCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    directoryTreeCapture);
            }

            if (componentInspectionCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    componentInspectionCapture);
            }

            if (componentRemovalCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    componentRemovalCapture);
            }

            if (serializedPropertyMutationCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    serializedPropertyMutationCapture);
            }

            if (copyFileCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    copyFileCapture);
            }

            if (appendFileCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    appendFileCapture);
            }

            if (sceneSaveCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    sceneSaveCapture);
            }

            if (sceneInspectionCapture !=
                null)
            {
                runtime.AddAgentObserver(
                    sceneInspectionCapture);
            }

            runtime.UseProvider(
                CreateProvider(
                    configuration));

            bool hasImageProvider = ConfigurePhaseFallbackProviders(
                runtime,
                agentId,
                coordinationOnly,
                reviewMode,
                configuration);
            humanVisualReview.Enabled = !hasImageProvider && humanVisualEvidenceSink != null;
            if (humanVisualReview.Enabled && RequiresGameplayReadinessReport(title + "\n" + description))
                humanVisualEvidenceSink("[]");

            if (!coordinationOnly)
                AddCapabilityIfMissing(configuration.EnabledTools, "inspect_gameplay_readiness");
            ConfigureCapabilities(
                runtime,
                configuration,
                capabilityServices);

            AIRuntimeEditorRuntimePluginContext activityContext =
                new AIRuntimeEditorRuntimePluginContext(
                    "Kanban",
                    agentId,
                    string.IsNullOrWhiteSpace(title)
                        ? "Untitled Kanban task"
                        : title,
                    coordinationOnly
                        ? "Decomposition"
                        : reviewMode
                            ? "Review"
                            : "Implementation",
                    correlationId);

            using IDisposable runtimePluginAttachments =
                AIRuntimeEditorRuntimePluginRegistry.Attach(
                    runtime,
                    activityContext);

            activityContext.Report(
                AIRuntimeAgentActivityKind.Status,
                "Preparing task execution.");

            try
            {

            string kanbanTaskContextEvidence =
                await runtime.ExecuteCapabilityAsync(
                    "get_kanban_task_context",
                    "{}",
                    cancellationToken);

            IReadOnlyCollection<string> structuredReviewArtifactPaths =
                ExtractStructuredReviewArtifactPaths(
                    kanbanTaskContextEvidence);

            IReadOnlyCollection<string> structuredProducedArtifactPaths =
                ExtractStructuredProducedArtifactPaths(
                    kanbanTaskContextEvidence);

            artifactOwnership.Activate(
                structuredProducedArtifactPaths,
                (structuredReviewArtifactPaths ?? Array.Empty<string>()).Concat(
                    ExtractCompletedDependencyArtifactPaths(kanbanTaskContextEvidence)),
                ExtractStructuredMutatedResourceKeys(
                    kanbanTaskContextEvidence),
                !coordinationOnly &&
                IsStructuredChildTask(
                    kanbanTaskContextEvidence));

            artifactOwnership.SetSiblingOwnershipContext(kanbanTaskContextEvidence);

            string gameplayReadinessEvidence = null;
            if (!coordinationOnly && RequiresGameplayReadinessReport(title + "\n" + description))
            {
                string readinessScene = (structuredReviewArtifactPaths ?? Array.Empty<string>())
                    .Concat(ExtractCompletedDependencyArtifactPaths(kanbanTaskContextEvidence))
                    .FirstOrDefault(path => path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase));
                if (string.IsNullOrWhiteSpace(readinessScene)) readinessScene = ExtractExplicitScenePath(effectiveRequest);
                if (!string.IsNullOrWhiteSpace(readinessScene))
                {
                    activityContext.Report(AIRuntimeAgentActivityKind.Status, "Preparing combined gameplay readiness report.");
                    try
                    {
                        gameplayReadinessEvidence = await CaptureBoundedReviewEvidenceAsync(
                            token => runtime.ExecuteCapabilityAsync("inspect_gameplay_readiness",
                                JsonConvert.SerializeObject(new { scenePath = readinessScene, includeReferencedPrefabs = true, runStartupCheck = false }), token),
                            cancellationToken, TimeSpan.FromMinutes(2));
                    }
                    catch (Exception error) when (!(error is OperationCanceledException))
                    {
                        gameplayReadinessEvidence = "[GameplayEvidenceUnavailable] " + error.Message +
                            " This is an evidence gap, never a passing gameplay verification.";
                    }
                }
            }


            string scaffoldingRoot =
                requiresDirectoryScaffolding
                    ? ExtractScaffoldingRoot(
                        effectiveRequest)
                    : null;

            // Dependency titles and historical handoffs can mention folder
            // setup on a verification-only card. Inferred preflight work is
            // optional and must respect the current card's actual outputs.
            if (reviewMode || coordinationOnly ||
                !artifactOwnership.CanCreateDirectory("Assets/" + scaffoldingRoot))
            {
                scaffoldingRoot = null;
                requiresDirectoryScaffolding = false;
            }

            if (!string.IsNullOrWhiteSpace(
                    scaffoldingRoot))
            {
                await EnsureScaffoldingRootAsync(
                    runtime,
                    scaffoldingRoot,
                    cancellationToken);
            }

            AgentDefinition definition =
                new AgentDefinition(
                    agentId.Trim(),
                    string.IsNullOrWhiteSpace(
                        configuration.AgentDisplayName)
                        ? agentId.Trim()
                        : configuration.AgentDisplayName.Trim(),
                    configuration.AgentDescription?.Trim() ??
                        string.Empty,
                    BuildSystemPrompt(
                        configuration.AgentSystemPrompt,
                        requiresRootFileList &&
                        !reviewMode,
                        requiresFileContentRead,
                        requiresComponentInspection:
                            false) +
                    (requiresSceneAuthoring
                        ? "\n\n" + SceneAuthoringSystemPrompt
                        : string.Empty) +
                    (requiresDirectoryScaffolding
                        ? "\n\n" + DirectoryScaffoldingSystemPrompt
                        : string.Empty));

            definition.AttachProfile(
                new AgentProfile(
                    definition.Id)
                {
                    Temperature =
                        configuration.AgentTemperature,

                    MaxTokens =
                        Math.Max(
                            0,
                            configuration.AgentMaxTokens)
                });

            if (configuration.EnableTools)
            {
                foreach (
                    string capability
                    in NormalizeCapabilities(
                        configuration.EnabledTools))
                {
                    // The runner executes this capability immediately before
                    // the model run and embeds the authoritative result in
                    // the prompt. Exposing it again invites models to repeat
                    // an identical read until the conversation loop guard
                    // terminates the task.
                    if (string.Equals(
                            capability,
                            "get_kanban_task_context",
                            StringComparison.Ordinal))
                    {
                        continue;
                    }

                    if (verifiesAppendFile &&
                        string.Equals(
                            capability,
                            "write_file",
                            StringComparison.Ordinal))
                    {
                        continue;
                    }

                    if ((verifiesAppendFile ||
                         verifiesCopyFile) &&
                        string.Equals(
                            capability,
                            "read_file",
                            StringComparison.Ordinal))
                    {
                        continue;
                    }

                    if (reviewMode &&
                        requiresDirectoryExistenceCheck &&
                        string.Equals(
                            capability,
                            "file_exists",
                            StringComparison.Ordinal))
                    {
                        // file_exists intentionally checks files only. Do
                        // not expose it during a directory acceptance review,
                        // where it produces a misleading false result.
                        continue;
                    }

                    if (reviewMode &&
                        standaloneMaterialAssetReview &&
                        (string.Equals(
                             capability,
                             "get_serialized_properties",
                             StringComparison.Ordinal) ||
                         string.Equals(
                             capability,
                             "file_exists",
                             StringComparison.Ordinal) ||
                         string.Equals(
                             capability,
                             "get_file_info",
                             StringComparison.Ordinal) ||
                         string.Equals(
                             capability,
                             "read_file",
                             StringComparison.Ordinal)))
                    {
                        // AIRuntime has already inspected each exact material
                        // instance and embeds that result in the review prompt.
                        // Re-exposing these generic read capabilities invites
                        // contradictory duplicate checks without the instance
                        // target used by the authoritative material evidence.
                        continue;
                    }

                    if (reviewMode &&
                        hasExplicitReviewFilePaths &&
                        string.Equals(
                            capability,
                            "read_file",
                            StringComparison.Ordinal))
                    {
                        // AIRuntime pre-reads the exact explicit file scope
                        // before the review starts. Keeping a second generic
                        // reader enabled lets filenames that appear only in
                        // negative prose ("do not create X") escape that
                        // scope and destabilize the review.
                        continue;
                    }

                    definition.AllowCapability(
                        capability);
                }
            }

            AgentRegistry registry =
                new AgentRegistry();

            registry.Register(
                definition);

            AgentExecutor executor =
                new AgentExecutor(
                    registry,
                    new AgentSessionManager(
                        registry),
                    runtime);

            string liveSelectionEvidence =
                null;

            string rootFileEvidence =
                null;

            string fileContentPath =
                null;

            string fileContentEvidence =
                null;

            string compilationEvidence =
                null;

            string directoryExistenceEvidence =
                null;

            string componentInspectionEvidence =
                null;

            string materialAssetEvidence =
                null;

            string prefabAssetEvidence =
                null;

            string componentInspectionPreflightIssue =
                null;

            string hierarchyTreeEvidence =
                null;

            if (reviewMode)
            {
                prefabAssetEvidence =
                    await CaptureProducedPrefabReviewEvidenceAsync(
                        runtime,
                        structuredProducedArtifactPaths,
                        cancellationToken);
            }

            if (requiresMaterialAssetEvidence)
            {
                materialAssetEvidence =
                    await CaptureMaterialAssetReviewEvidenceAsync(
                        runtime,
                        classificationRequest,
                        cancellationToken);
            }

            CopyVerificationPaths copyVerificationPaths =
                verifiesCopyFile
                    ? ResolveCopyVerificationPaths(
                        effectiveRequest,
                        configuration.WorkspaceRoot.Trim())
                    : null;

            string copyVerificationEvidence =
                reviewMode &&
                verifiesCopyFile
                    ? await VerifyCopiedFileAsync(
                        runtime,
                        copyVerificationPaths,
                        cancellationToken)
                    : null;

            string appendVerificationPath =
                verifiesAppendFile
                    ? ExtractRequestedFilePath(
                        effectiveRequest)
                    : null;

            string appendVerificationBaseline =
                verifiesAppendFile
                    ? ExtractAppendBaselineContent(
                        effectiveRequest)
                    : null;

            string appendVerificationContent =
                verifiesAppendFile
                    ? ExtractExactAppendContent(
                        effectiveRequest)
                    : null;

            string appendVerificationEvidence =
                reviewMode &&
                verifiesAppendFile
                    ? await VerifyAppendedFileAsync(
                        runtime,
                        appendVerificationPath,
                        appendVerificationBaseline,
                        appendVerificationContent,
                        false,
                        cancellationToken)
                    : null;

            if (!reviewMode &&
                verifiesAppendFile)
            {
                if (string.IsNullOrWhiteSpace(
                        appendVerificationPath) ||
                    appendVerificationBaseline ==
                    null ||
                    appendVerificationContent ==
                    null)
                {
                    throw new InvalidOperationException(
                        "Append verification requires an exact target path and an explicit original-text baseline.");
                }


                await runtime.ExecuteCapabilityAsync(
                    "write_file",
                    JsonConvert.SerializeObject(
                        new
                        {
                            path = appendVerificationPath,
                            content = appendVerificationBaseline
                        }),
                    cancellationToken);
            }

            SerializedBooleanVerification serializedBooleanVerification =
                null;

            bool componentInspectionHasNamedTarget =
                requiresComponentInspection &&
                !string.IsNullOrWhiteSpace(
                    ExtractReviewGameObjectName(
                        evidenceTargetRequest));

            if ((reviewMode &&
                 (requiresEditorSelection ||
                  (requiresFileContentRead && !sourceArtifactScope &&
                   ResolveRequestedFilePaths(
                      classificationRequest,
                      null,
                      configuration.WorkspaceRoot.Trim(),
                      structuredReviewArtifactPaths).Count ==
                      0))) ||
                (requiresComponentInspection &&
                 !componentInspectionHasNamedTarget) ||
                requiresSelectedHierarchyTree)
            {
                liveSelectionEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "get_editor_selection",
                        "{\"includeComponents\":true}",
                        cancellationToken);
            }

            if (requiresComponentInspection && string.IsNullOrWhiteSpace(gameplayReadinessEvidence))
            {
                string explicitScenePath =
                    ExtractExplicitScenePath(
                        evidenceTargetRequest);

                try
                {
                    if (reviewMode)
                        activityContext.Report(AIRuntimeAgentActivityKind.Status,
                            "Review preparation: loading scene for inspection: " + explicitScenePath);
                    EnsureExistingSceneLoadedForInspection(
                        explicitScenePath);

                    if (reviewMode &&
                        !string.IsNullOrWhiteSpace(explicitScenePath))
                    {
                        componentInspectionEvidence =
                            await CaptureBoundedReviewEvidenceAsync(
                                token => CaptureSceneRootComponentEvidenceAsync(
                                    runtime,
                                    explicitScenePath,
                                    evidenceTargetRequest,
                                    token,
                                    artifactOwnership.CanReadPath,
                                    stage => activityContext.Report(AIRuntimeAgentActivityKind.Status, stage)),
                                cancellationToken,
                                TimeSpan.FromMinutes(2));
                    }
                }
                catch (InvalidOperationException exception) when (reviewMode)
                {
                    // Missing review evidence is a review finding, not an
                    // execution crash. Preserve the authoritative preflight
                    // issue in the prompt so the reviewer can return FAIL.
                    componentInspectionPreflightIssue =
                        exception.Message;
                }
            }

            if (requiresComponentInspection &&
                string.IsNullOrWhiteSpace(gameplayReadinessEvidence) &&
                string.IsNullOrWhiteSpace(
                    componentInspectionEvidence) &&
                string.IsNullOrWhiteSpace(
                    componentInspectionPreflightIssue))
            {
                int selectedGameObjectId =
                    0;

                try
                {
                    selectedGameObjectId =
                        await ResolveTaskGameObjectIdAsync(
                            runtime,
                            evidenceTargetRequest,
                            liveSelectionEvidence,
                            cancellationToken);
                }
                catch (InvalidOperationException exception)
                {
                    // Target inference is a convenience owned by AIRuntime,
                    // not a reason to prevent the configured provider from
                    // seeing an otherwise executable card. Let the agent use
                    // find_game_objects / inspect_editor_component itself.
                    componentInspectionPreflightIssue =
                        exception.Message;
                }

                if (selectedGameObjectId !=
                    0)
                {
                    componentInspectionEvidence =
                        await CaptureSingleComponentReviewEvidenceAsync(
                            runtime,
                            selectedGameObjectId,
                            reviewMode,
                            cancellationToken,
                            artifactOwnership.CanReadPath);
                }
            }

            componentInspectionEvidence =
                AppendProducedPrefabReviewEvidence(
                    componentInspectionEvidence,
                    prefabAssetEvidence,
                    reviewMode);

            if (verifiesSetSerializedProperty)
            {
                int componentInstanceId =
                    ResolveVerificationComponentId(
                        effectiveRequest,
                        componentInspectionEvidence);

                string serializedPropertiesEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "get_serialized_properties",
                        JsonConvert.SerializeObject(
                            new
                            {
                                componentInstanceId,
                                includeHidden = true,
                                maxProperties = 256
                            }),
                        cancellationToken);

                serializedBooleanVerification =
                    ResolveSerializedBooleanVerification(
                        componentInstanceId,
                        serializedPropertiesEvidence);
            }

            if (requiresSelectedHierarchyTree)
            {
                int selectedGameObjectId =
                    ResolveSingleSelectedGameObjectId(
                        liveSelectionEvidence);

                hierarchyTreeEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "get_hierarchy_tree",
                        JsonConvert.SerializeObject(
                            new
                            {
                                rootObjectInstanceId =
                                    selectedGameObjectId,

                                includeInactive =
                                    true,

                                includeComponents =
                                    false,

                                maxDepth =
                                    8,

                                maxObjects =
                                    250
                            }),
                        cancellationToken);
            }

            if (reviewMode &&
                requiresFileContentRead)
            {
                IReadOnlyList<string> requestedFilePaths = sourceArtifactScope
                    ? ResolveOwnedSourceReviewPaths(structuredProducedArtifactPaths, configuration.WorkspaceRoot.Trim())
                    : ResolveRequestedFilePaths(
                        classificationRequest,
                        liveSelectionEvidence,
                        configuration.WorkspaceRoot.Trim(),
                        structuredReviewArtifactPaths);

                if (requestedFilePaths.Count >
                    0)
                {
                    JObject contents =
                        new JObject();

                    foreach (
                        string requestedFilePath
                        in requestedFilePaths)
                    {
                        try
                        {
                            contents[requestedFilePath] =
                                await runtime.ExecuteCapabilityAsync(
                                "read_file",
                                JsonConvert.SerializeObject(
                                    new
                                    {
                                        path =
                                            requestedFilePath,

                                        requireExactPath =
                                            true
                                    }),
                                cancellationToken);
                        }
                        catch (FileNotFoundException exception)
                        {
                            // A missing declared artifact is review evidence,
                            // not a runner failure. Preserve it for the model
                            // to issue an ImplementationDefect verdict.
                            contents[requestedFilePath] =
                                BuildMissingReviewArtifactEvidence(
                                    requestedFilePath,
                                    exception.Message);
                        }
                        catch (DirectoryNotFoundException exception)
                        {
                            contents[requestedFilePath] =
                                BuildMissingReviewArtifactEvidence(
                                    requestedFilePath,
                                    exception.Message);
                        }
                    }

                    fileContentPath =
                        string.Join(
                            ", ",
                            requestedFilePaths);

                    fileContentEvidence =
                        contents.ToString(
                            Formatting.Indented);
                }
            }

            if (reviewMode &&
                requiresCompilationCheck)
            {
                compilationEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "get_compilation_errors",
                        "{\"includeWarnings\":true,\"maxMessages\":200}",
                        cancellationToken);

                compilationEvidence =
                    ScopeCompilationEvidence(
                        compilationEvidence,
                        classificationRequest);
            }

            if (reviewMode)
            {
                if (requiredExistingDirectories.Count > 0)
                {
                    JArray directories =
                        new JArray();

                    foreach (string requiredDirectory in requiredExistingDirectories)
                    {
                        string infoJson =
                            await runtime.ExecuteCapabilityAsync(
                                "get_file_info",
                                JsonConvert.SerializeObject(
                                    new
                                    {
                                        path = requiredDirectory
                                    }),
                                cancellationToken);

                        JObject info =
                            JObject.Parse(infoJson ?? "{}");

                        bool existsAsDirectory =
                            info["Exists"]?.Value<bool>() == true &&
                            info["IsDirectory"]?.Value<bool>() == true;

                        JToken directDirectories =
                            new JArray();

                        if (existsAsDirectory)
                        {
                            string listingJson =
                                await runtime.ExecuteCapabilityAsync(
                                    "list_directory",
                                    JsonConvert.SerializeObject(
                                        new
                                        {
                                            path = requiredDirectory,
                                            entryType = "Directories",
                                            outputFormat = "PathsOnly",
                                            includeMetaFiles = false
                                        }),
                                    cancellationToken);

                            directDirectories =
                                JToken.Parse(listingJson ?? "[]");
                        }

                        directories.Add(
                            new JObject
                            {
                                ["Path"] = requiredDirectory,
                                ["ExistsAsDirectory"] = existsAsDirectory,
                                ["DirectoryInfo"] = info,
                                ["DirectDirectories"] = directDirectories
                            });
                    }

                    directoryExistenceEvidence =
                        new JObject
                        {
                            ["RequestedDirectoryCount"] = directories.Count,
                            ["AllRequiredDirectoriesExist"] =
                                directories
                                    .OfType<JObject>()
                                    .All(
                                        directory =>
                                            directory["ExistsAsDirectory"]?.Value<bool>() == true),
                            ["Directories"] = directories
                        }.ToString(Formatting.Indented);
                }
            }

            if (reviewMode &&
                requiresRootFileList)
            {
                rootFileEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "list_directory",
                        "{\"path\":\".\",\"entryType\":\"Files\",\"outputFormat\":\"PathsOnly\"}",
                        cancellationToken);
            }

            if (reviewMode &&
                HasTargetArtifactCompilationErrors(
                    compilationEvidence))
            {
                reviewEvidenceSink?.Invoke(
                    BuildReviewEvidenceBundle(
                        kanbanTaskContextEvidence,
                        liveSelectionEvidence,
                        rootFileEvidence,
                        fileContentEvidence,
                        componentInspectionEvidence,
                        compilationEvidence,
                        directoryExistenceEvidence,
                        materialAssetEvidence,
                        hierarchyTreeEvidence,
                        componentInspectionCapture,
                        sceneInspectionCapture,
                        fileReadCapture,
                        directoryTreeCapture));

                return BuildTargetCompilationPreflightFailure(
                    compilationEvidence);
            }

            AgentRunResult result;

            string componentRemovalVerification =
                null;


            using (
                ProviderStreamingContext.Begin(
                    text =>
                    {
                    }))
            {
                string runPrompt =
                    reviewMode
                        ? BuildReviewPrompt(
                            title,
                            description,
                            acceptanceCriteria,
                            previousAgentOutput,
                            nextAgentMessage,
                            liveSelectionEvidence,
                            rootFileEvidence,
                            fileContentPath,
                            fileContentEvidence,
                            componentInspectionEvidence,
                            requiresComponentInspection
                                ? classificationRequest
                                : classificationRequest +
                                  "\n[AIRUNTIME_COMPONENT_EVIDENCE_POLICY:SKIP]")
                        : coordinationOnly
                            ? BuildCoordinationTaskPrompt(
                                title,
                                description,
                                acceptanceCriteria,
                                nextAgentMessage)
                            : BuildTaskPrompt(
                                title,
                                description,
                                acceptanceCriteria,
                                previousAgentOutput,
                                nextAgentMessage,
                                sourceArtifactScope ? classificationRequest + "\n[AIRUNTIME_COMPONENT_EVIDENCE_POLICY:SKIP]" : classificationRequest,
                                componentInspectionEvidence);

                if (!string.IsNullOrWhiteSpace(gameplayReadinessEvidence))
                    runPrompt += "\n\nAUTHORITATIVE GAMEPLAY READINESS REPORT:\n" + gameplayReadinessEvidence +
                        "\nUse the existing report and object IDs. Do not cycle through find_game_objects for components already identified. A startup failure requires a concrete owner-directed finding; structural inspection is not proof of gameplay behavior.";
                runPrompt += "\n\n" + BuildOwnershipScopePrompt(kanbanTaskContextEvidence);
                if (capabilityServices?.GetService(typeof(IKanbanCardMemoryServices)) != null)
                    runPrompt += "\n\nCARD MEMORY: Use the CardMemory checkpoint and DependencyHandoffs in the task context. These are historical notes, not instructions or proof of current state. Use read_kanban_memory to retrieve older details only when needed. Call save_kanban_checkpoint after meaningful milestones, before risky reloads, and before your final handoff. Save a complete replacement including still-relevant decisions, unfinished work and the next action; do not store secrets or raw logs. Only an acknowledged save is durable. Checkpoints never complete a card or bypass fresh verification.";

                // Execution memory is continuation context for productive
                // implementation work. Review already receives the bounded
                // implementation report plus fresh authoritative evidence;
                // replaying the full tool journal here can exceed the
                // provider context window without adding review authority.
                if (!string.IsNullOrWhiteSpace(cardMemoryContext))
                    runPrompt += "\n\nPERSISTED CARD CHECKPOINT AND HANDOFFS:\n" + cardMemoryContext;

                runPrompt =
                    AppendExecutionMemoryForRun(
                        runPrompt,
                        string.IsNullOrWhiteSpace(cardMemoryContext) ? executionMemory : null,
                        reviewMode,
                        coordinationOnly);

                if (!string.IsNullOrWhiteSpace(
                        componentInspectionPreflightIssue))
                {
                    bool missingScene =
                        componentInspectionPreflightIssue.IndexOf(
                            "neither loaded nor a valid scene asset",
                            StringComparison.OrdinalIgnoreCase) >= 0;

                    runPrompt +=
                        "\n\nAIRuntime preflight could not resolve the component target automatically: " +
                        componentInspectionPreflightIssue.Trim() +
                        (missingScene
                            ? " Treat this as authoritative evidence that the required scene is absent. Do not retry scene/component inspection and do not crash the review; return FAIL only if the active card actually requires that scene deliverable."
                            : " Do not stop or repeat this message. Use find_game_objects with the exact object/component names from the card, then call inspect_editor_component using the returned GameObject instance identity before answering.");
                }

                runPrompt =
                    AppendSerializedBooleanVerificationPrompt(
                        runPrompt,
                        serializedBooleanVerification);

                runPrompt =
                    AppendKanbanTaskContextEvidence(
                        runPrompt,
                        kanbanTaskContextEvidence,
                        reviewMode);

                runPrompt =
                    AppendHierarchyTreeEvidence(
                        runPrompt,
                        hierarchyTreeEvidence,
                        reviewMode);

                runPrompt =
                    AppendCopyVerificationEvidence(
                        runPrompt,
                        copyVerificationPaths,
                        copyVerificationEvidence,
                        reviewMode);

                runPrompt =
                    AppendGeneralReviewEvidence(
                        runPrompt,
                        compilationEvidence,
                        directoryExistenceEvidence,
                        reviewMode);

                runPrompt =
                    AppendMaterialAssetReviewEvidence(
                        runPrompt,
                        materialAssetEvidence,
                        reviewMode);

                runPrompt =
                    AppendAppendVerificationEvidence(
                        runPrompt,
                        appendVerificationPath,
                        appendVerificationBaseline,
                        appendVerificationContent,
                        appendVerificationEvidence,
                        reviewMode);

                if (humanVisualReview.Enabled)
                    runPrompt += "\n\nHUMAN VISUAL REVIEW: No configured provider in this run supports images. Save representative screenshots with capture_game_view or capture_camera_view under Logs/KanbanVisualReviews/ for the human, and continue all feasible nonvisual checks. Screenshot paths and metadata are not visual proof. Clearly list the visual checks the human must perform. In review, judge the automated requirements normally (FAIL for actual defects or missing nonvisual evidence); a PASS applies only to automated checks. AIRuntime separately blocks final acceptance until human visual approval.";

                Task<AgentRunResult> agentRunTask =
                    executor.RunAsync(
                        definition.Id,
                        runPrompt,
                        cancellationToken);

                // Post-run recovery can invoke the provider again and thereby
                // execute additional Unity Editor capabilities. Preserve the
                // Editor synchronization context here; moving only the final
                // continuation to the thread pool made those recovery calls
                // fail nondeterministically with Unity's main-thread guard.
                // AIRuntimeKanbanWindow independently queues the final board
                // transition, so it no longer needs this runner continuation
                // to escape UnitySynchronizationContext.
                result =
                    await agentRunTask;

                Debug.Log(
                    $"[Kanban] Runner received completed AgentRunResult for '{title}' (review={reviewMode}, status={result?.Status}).");

                if (!reviewMode &&
                    verifiesAppendFile &&
                    result.Failed &&
                    IsMissingAppendContentFailure(
                        result.ErrorMessage))
                {
                    string appendPath =
                        ExtractRequestedFilePath(
                            effectiveRequest);

                    string appendContent =
                        ExtractExactAppendContent(
                            effectiveRequest);

                    if (!string.IsNullOrWhiteSpace(
                            appendPath) &&
                        appendContent !=
                        null)
                    {
                        activityContext.Report(
                            AIRuntimeAgentActivityKind.Retry,
                            "Retrying append with validated content arguments.");

                        result =
                            await executor.RunAsync(
                                definition.Id,
                                BuildAppendFileRetryPrompt(
                                    appendPath,
                                    appendContent),
                                cancellationToken);
                    }
                }

                if (!reviewMode &&
                    requiresComponentRemoval &&
                    result.Failed &&
                    IsMissingComponentIdentityFailure(
                        result.ErrorMessage))
                {
                    string removalArguments =
                        ResolveExactComponentRemovalArguments(
                            effectiveRequest,
                            componentInspectionEvidence);

                    if (!string.IsNullOrWhiteSpace(
                            removalArguments))
                    {
                        activityContext.Report(
                            AIRuntimeAgentActivityKind.Retry,
                            "Retrying component removal with resolved identity evidence.");

                        result =
                            await executor.RunAsync(
                                definition.Id,
                                BuildRemoveComponentRetryPrompt(
                                    removalArguments),
                                cancellationToken);
                    }
                }

                if (!reviewMode &&
                    verifiesSetSerializedProperty &&
                    result.Failed &&
                    IsMissingValueJsonFailure(
                        result.ErrorMessage) &&
                    serializedPropertyMutationCapture.CompletedCount == 0)
                {
                    activityContext.Report(
                        AIRuntimeAgentActivityKind.Retry,
                        "Retrying serialized-property mutation with validated value JSON.");

                    result =
                        await executor.RunAsync(
                            definition.Id,
                            BuildSerializedBooleanRetryPrompt(
                                serializedBooleanVerification),
                            cancellationToken);
                }

                if (!reviewMode &&
                    result.Succeeded &&
                    componentRemovalCapture !=
                    null &&
                    componentRemovalCapture.CompletedCount >
                    0)
                {
                    componentRemovalVerification =
                        await VerifyRemovedComponentAsync(
                            runtime,
                            componentRemovalCapture.Result,
                            componentInspectionEvidence,
                            cancellationToken);
                }

                if (!reviewMode &&
                    verifiesSetSerializedProperty)
                {
                    await VerifyAndRestoreSerializedBooleanAsync(
                        runtime,
                        serializedBooleanVerification,
                        serializedPropertyMutationCapture,
                        cancellationToken);
                }

                if (!reviewMode &&
                    result.Succeeded &&
                    requiresPersistedSceneObject &&
                    (sceneSaveCapture == null ||
                     sceneSaveCapture.CompletedCount == 0))
                {
                    activityContext.Report(
                        AIRuntimeAgentActivityKind.Retry,
                        "Retrying scene persistence verification.");

                    result =
                        await executor.RunAsync(
                            definition.Id,
                            "The implementation work already completed, but AIRuntime did not receive a successful save_editor_scene result. " +
                            $"Do not recreate or duplicate GameObject '{expectedCreatedGameObject}'. Save the current scene now at the Assets/.../.unity path required by the active card, reusing its existing asset path when already saved. Then call inspect_scene for the exact ScenePath returned by save_editor_scene and provide a concise completion report.",
                            cancellationToken);
                }
            }

            if (!reviewMode &&
                result.Failed &&
                IsRepeatedCapabilityLoopFailure(
                    result.ErrorMessage) &&
                ((verifiesAppendFile &&
                  appendFileCapture != null &&
                  appendFileCapture.CompletedCount ==
                  1) ||
                 (verifiesCopyFile &&
                  copyFileCapture != null &&
                  copyFileCapture.CompletedCount ==
                  1)))
            {
                // The requested mutation already completed successfully. A
                // provider asking to repeat the identical call must not turn
                // that verified one-shot operation into a failed card.
                result.Status =
                    AgentRunStatus.Succeeded;
                result.Response =
                    "The capability completed exactly once. AIRuntime ignored the provider's duplicate request and verified the resulting state independently.";
                result.ErrorMessage =
                    null;
            }

            if (result.Succeeded)
            {
                activityContext.UpdateStage(
                    reviewMode
                        ? "Review verification"
                        : coordinationOnly
                            ? "Graph verification"
                            : "Verification",
                    "Checking completion evidence.");

                string response =
                    result.Response ??
                    string.Empty;
                if (reviewMode)
                    response = RejectContradictoryPrefabConstraintClaims(response, prefabAssetEvidence);

                string finalKanbanContextJson =
                    !reviewMode
                        ? await GetKanbanTaskContextAsync(
                            runtime,
                            cancellationToken)
                        : kanbanTaskContextEvidence;

                int finalDirectChildCount =
                    GetDirectChildCount(
                        finalKanbanContextJson);

                bool associationResolvedAsAtomicLeaf =
                    coordinationOnly &&
                    GetAssociationResolvedAsAtomicLeaf(
                        finalKanbanContextJson);

                if (coordinationOnly &&
                    finalDirectChildCount < 1 &&
                    !associationResolvedAsAtomicLeaf)
                {
                    throw new InvalidOperationException(
                        "Planning validation requires at least one execution-ready implementation or verification card before the project can leave Planning.");
                }

                if (!reviewMode &&
                    coordinationOnly)
                {
                    await VerifyDelegationSafetyAsync(
                        runtime,
                        cancellationToken);
                }

                if (!reviewMode &&
                    !coordinationOnly)
                {
                    VerifyAssetDeliverables(
                        effectiveRequest,
                        configuration.WorkspaceRoot.Trim(),
                        ExtractStructuredProducedArtifactPaths(
                            finalKanbanContextJson));

                    if (requiresPersistedSceneObject)
                    {
                        VerifyPersistedSceneObjectCompletion(
                            expectedCreatedGameObject,
                            sceneSaveCapture,
                            sceneInspectionCapture);
                    }

                    // Live scene-object claims are verified by the dedicated
                    // Review pass. Starting another Editor capability after
                    // RunCompleted made implementation completion depend on a
                    // fragile second Unity async chain.
                }

                if (!reviewMode &&
                    verifiesCopyFile)
                {
                    if (copyFileCapture == null ||
                        copyFileCapture.CompletedCount ==
                        0)
                    {
                        throw new InvalidOperationException(
                            "The model did not complete the required copy_file tool call.");
                    }

                    copyVerificationEvidence =
                        await VerifyCopiedFileAsync(
                            runtime,
                            copyVerificationPaths,
                            cancellationToken);

                    response =
                        response.TrimEnd() +
                        "\n\nAIRuntime copy verification:\n" +
                        copyVerificationEvidence;
                }

                if (!reviewMode &&
                    verifiesAppendFile)
                {
                    if (appendFileCapture == null ||
                        appendFileCapture.CompletedCount !=
                        1)
                    {
                        throw new InvalidOperationException(
                            "The model must complete exactly one append_file tool call.");
                    }

                    appendVerificationEvidence =
                        await VerifyAppendedFileAsync(
                            runtime,
                            appendVerificationPath,
                            appendVerificationBaseline,
                            appendVerificationContent,
                            true,
                            cancellationToken);

                    response =
                        response.TrimEnd() +
                        "\n\nAIRuntime append verification:\n" +
                        appendVerificationEvidence;
                }

                if (reviewMode)
                {
                    string reviewedResponse =
                        EnsureReviewVerdict(
                        response,
                        liveSelectionEvidence,
                        rootFileEvidence,
                        previousAgentOutput,
                        requiresFileContentRead,
                        fileContentEvidence,
                        fileReadCapture,
                        requiresDirectoryTree,
                        effectiveRequest,
                        directoryTreeCapture);

                    reviewEvidenceSink?.Invoke(
                        BuildReviewEvidenceBundle(
                            kanbanTaskContextEvidence,
                            liveSelectionEvidence,
                            rootFileEvidence,
                            fileContentEvidence,
                            componentInspectionEvidence,
                            compilationEvidence,
                            directoryExistenceEvidence,
                            materialAssetEvidence,
                            hierarchyTreeEvidence,
                            componentInspectionCapture,
                            sceneInspectionCapture,
                            fileReadCapture,
                            directoryTreeCapture));

                    activityContext.Report(
                        AIRuntimeAgentActivityKind.RunCompleted,
                        "Kanban review verification completed.");

                    return reviewedResponse;
                }

                if (requiresRootFileList)
                {
                    string verifiedResponse =
                        EnforceRootFileOutputContract(
                            response,
                            rootListCapture?.Result);

                    activityContext.Report(
                        AIRuntimeAgentActivityKind.RunCompleted,
                        "Kanban verification completed.");

                    return verifiedResponse;
                }


                if (requiresDirectoryTree)
                {
                    string verifiedResponse =
                        EnforceDirectoryTreeOutputContract(
                            directoryTreeCapture?.Result,
                            effectiveRequest);

                    activityContext.Report(
                        AIRuntimeAgentActivityKind.RunCompleted,
                        "Kanban verification completed.");

                    return verifiedResponse;
                }

                if (requiresFileContentRead &&
                    (fileReadCapture == null ||
                     fileReadCapture.CompletedCount ==
                     0))
                {
                    throw new InvalidOperationException(
                        "The model did not complete the required read_file tool call before answering the file-content request.");
                }

                if (requiresComponentInspection &&
                    string.IsNullOrWhiteSpace(
                        componentInspectionEvidence) &&
                    (componentInspectionCapture == null ||
                     componentInspectionCapture.CompletedCount ==
                     0))
                {
                    throw new InvalidOperationException(
                        "The model did not complete the required inspect_editor_component tool call before answering the component troubleshooting request.");
                }

                if (componentRemovalCapture !=
                        null &&
                    componentRemovalCapture.CompletedCount ==
                    0)
                {
                    throw new InvalidOperationException(
                        "The model did not complete the required remove_editor_component tool call before answering the component-removal request.");
                }

                if (!string.IsNullOrWhiteSpace(
                        componentRemovalVerification))
                {
                    response =
                        response.TrimEnd() +
                        "\n\nAIRuntime verification:\n" +
                        componentRemovalVerification;
                }

                activityContext.Report(
                    AIRuntimeAgentActivityKind.RunCompleted,
                    "Kanban verification completed.");

                return response;
            }

            if (result.Cancelled)
            {
                throw new OperationCanceledException(
                    cancellationToken);
            }

            string failureMessage =
                string.IsNullOrWhiteSpace(
                    result.ErrorMessage)
                    ? "The assigned agent did not complete the task."
                    : result.ErrorMessage;

            if (result.ProviderFailureKind.HasValue)
            {
                throw new ProviderExecutionException(
                    result.ProviderFailureKind.Value,
                    failureMessage);
            }

            throw new InvalidOperationException(
                failureMessage);
            }
            catch (OperationCanceledException)
            {
                activityContext.Report(
                    AIRuntimeAgentActivityKind.RunCancelled,
                    "Kanban run cancelled.");

                throw;
            }
            catch (Exception exception)
            {
                activityContext.Report(
                    AIRuntimeAgentActivityKind.RunFailed,
                    "Kanban run could not complete.",
                    exception.Message);

                throw;
            }
        }


        private static bool IsRepeatedCapabilityLoopFailure(
            string errorMessage)
        {
            return
                !string.IsNullOrWhiteSpace(
                    errorMessage) &&
                errorMessage.IndexOf(
                    "repeated the identical capability call",
                    StringComparison.OrdinalIgnoreCase) >=
                       0;
        }


        internal static bool ContainsUnexecutedToolMarkup(
            string result)
        {
            return !string.IsNullOrWhiteSpace(result) &&
                   Regex.IsMatch(
                       result,
                       @"<tool_calls?:[^>]+>|<tool_call:[^>]+>",
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static AIRuntimeEditorAgentConfiguration
            LoadConfiguration(
                string agentId)
        {
            string root =
                Path.Combine(
                    Environment.GetFolderPath(
                        Environment.SpecialFolder.MyDocuments),
                    "AIRuntime");

            if (!Directory.Exists(
                    root))
            {
                throw new DirectoryNotFoundException(
                    $"No saved agent folder exists at '{root}'.");
            }

            AIRuntimeEditorAgentConfiguration match =
                null;

            string matchPath =
                null;

            foreach (
                string path
                in Directory.GetFiles(
                    root,
                    "*.json",
                    SearchOption.AllDirectories))
            {
                try
                {
                    AIRuntimeEditorAgentConfiguration candidate =
                        JsonUtility.FromJson<AIRuntimeEditorAgentConfiguration>(
                            File.ReadAllText(
                                path,
                                Encoding.UTF8));

                    if (candidate == null ||
                        candidate.Version != 1 ||
                        !string.Equals(
                            AIRuntimeEditorAgentConfigurationPaths.ResolveInstanceId(
                                candidate.AgentId,
                                path),
                            agentId?.Trim(),
                            StringComparison.Ordinal))
                    {
                        continue;
                    }

                    if (match == null ||
                        AIRuntimeEditorAgentConfigurationPaths.ShouldPrefer(
                            agentId,
                            path,
                            matchPath))
                    {
                        match =
                            candidate;

                        matchPath =
                            path;
                    }
                }
                catch (Exception exception)
                    when (
                        exception is IOException ||
                        exception is UnauthorizedAccessException ||
                        exception is ArgumentException)
                {
                    // Ignore unrelated and unreadable JSON documents.
                }
            }

            return match ??
                   throw new FileNotFoundException(
                       $"No saved agent configuration was found for '{agentId}'.");
        }


        private static void ValidateConfiguration(
            AIRuntimeEditorAgentConfiguration configuration)
        {
            if (!configuration.UseAgent)
            {
                throw new InvalidOperationException(
                    "The saved configuration is not configured for agent execution.");
            }

            if (string.IsNullOrWhiteSpace(
                    configuration.WorkspaceRoot) ||
                !Directory.Exists(
                    configuration.WorkspaceRoot))
            {
                throw new InvalidOperationException(
                    "The saved agent workspace does not exist.");
            }

            AIRuntimeEditorPluginCatalog.ValidateProviderSettings(
                configuration.Provider,
                CreateProviderSettings(
                    configuration));
        }


        private static IProvider CreateProvider(
            AIRuntimeEditorAgentConfiguration configuration)
        {
            return AIRuntimeEditorPluginCatalog.CreateProvider(
                configuration.Provider,
                CreateProviderSettings(
                    configuration));
        }


        private static bool ConfigurePhaseFallbackProviders(
            RuntimeFacade runtime,
            string primaryAgentId,
            bool coordinationOnly,
            bool reviewMode,
            AIRuntimeEditorAgentConfiguration primaryConfiguration)
        {
            bool hasImageProvider = primaryConfiguration.SupportsImageInput;
            HashSet<string> providerKeys =
                new HashSet<string>(
                    StringComparer.OrdinalIgnoreCase)
                {
                    BuildProviderKey(
                        primaryConfiguration)
                };

            foreach (string fallbackAgentId in
                     ResolvePhaseFallbackAgentIds(
                         primaryAgentId,
                         coordinationOnly,
                         reviewMode))
            {
                try
                {
                    AIRuntimeEditorAgentConfiguration fallback =
                        LoadConfiguration(
                            fallbackAgentId);

                    ValidateConfiguration(
                        fallback);

                    if (!providerKeys.Add(
                            BuildProviderKey(fallback)))
                    {
                        continue;
                    }

                    runtime.AddFallbackProvider(
                        CreateProvider(fallback));
                    hasImageProvider |= fallback.SupportsImageInput;
                }
                catch (Exception exception)
                    when (
                        exception is IOException ||
                        exception is UnauthorizedAccessException ||
                        exception is ArgumentException ||
                        exception is InvalidOperationException)
                {
                    Debug.LogWarning(
                        $"[Kanban] Skipped unavailable provider fallback '{fallbackAgentId}': {exception.Message}");
                }
            }
            return hasImageProvider;
        }


        internal static IReadOnlyList<string> ResolvePhaseFallbackAgentIds(
            string primaryAgentId,
            bool coordinationOnly,
            bool reviewMode)
        {
            string[] orderedRoles =
                coordinationOnly
                    ? new[] { "ReviewAgent", "DeveloperAgent" }
                    : reviewMode
                        ? new[] { "ArchitectAgent", "DeveloperAgent" }
                        : new[] { "ArchitectAgent", "ReviewAgent" };

            return orderedRoles
                .Where(
                    role =>
                        !string.Equals(
                            role,
                            primaryAgentId?.Trim(),
                            StringComparison.Ordinal))
                .Distinct(StringComparer.Ordinal)
                .ToArray();
        }


        private static string BuildProviderKey(
            AIRuntimeEditorAgentConfiguration configuration)
        {
            return string.Join(
                "|",
                configuration?.Provider?.Trim() ?? string.Empty,
                configuration?.Model?.Trim() ?? string.Empty,
                configuration?.BaseUrl?.Trim() ?? string.Empty);
        }


        private static AIRuntimeEditorProviderSettings CreateProviderSettings(
            AIRuntimeEditorAgentConfiguration configuration)
        {
            return new AIRuntimeEditorProviderSettings
            {
                ApiKey = configuration.ApiKey ?? string.Empty,
                Model = configuration.Model?.Trim() ?? string.Empty,
                SupportsImageInput = configuration.SupportsImageInput,
                BaseUrl = configuration.BaseUrl?.Trim() ?? string.Empty,
                HttpReferer = configuration.HttpReferer ?? string.Empty,
                ApplicationTitle = configuration.ApplicationTitle ?? string.Empty
            };
        }


        private static void ConfigureCapabilities(
            RuntimeFacade runtime,
            AIRuntimeEditorAgentConfiguration configuration,
            IServiceProvider capabilityServices)
        {
            if (!configuration.EnableTools)
                return;

            foreach (
                string capabilityName
                in NormalizeCapabilities(
                    configuration.EnabledTools))
            {
                runtime.RegisterCapability(
                    CreateCapability(
                        capabilityName,
                        capabilityServices));
            }

            runtime.AllowPermission(
                CapabilityPermission.ReadOnly);

            ApplyUnattendedPermission(
                runtime,
                CapabilityPermission.Write,
                configuration.WritePermission);

            ApplyUnattendedPermission(
                runtime,
                CapabilityPermission.Destructive,
                configuration.DestructivePermission);

            ApplyUnattendedPermission(
                runtime,
                CapabilityPermission.Process,
                configuration.ProcessPermission);

            ApplyUnattendedPermission(
                runtime,
                CapabilityPermission.Network,
                configuration.NetworkPermission);
        }


        private static ICapability CreateCapability(
            string name,
            IServiceProvider capabilityServices)
        {
            return AIRuntimeEditorPluginCatalog.CreateCapability(
                name,
                AIRuntimeEditorPluginHost.Kanban,
                capabilityServices);
        }


        private static void ApplyUnattendedPermission(
            RuntimeFacade runtime,
            CapabilityPermission permission,
            string savedDecision)
        {
            if (!Enum.TryParse(
                    savedDecision,
                    ignoreCase: true,
                    out CapabilityPermissionDecision decision))
            {
                decision =
                    CapabilityPermissionDecision.RequireApproval;
            }

            switch (decision)
            {
                case CapabilityPermissionDecision.Allow:
                    runtime.AllowPermission(
                        permission);
                    return;

                case CapabilityPermissionDecision.Deny:
                    runtime.DenyPermission(
                        permission);
                    return;

                case CapabilityPermissionDecision.RequireApproval:
                    runtime.DenyPermission(
                        permission);
                    return;

                default:
                    throw new ArgumentOutOfRangeException(
                        nameof(savedDecision));
            }
        }


        private static IEnumerable<string> NormalizeCapabilities(
            IEnumerable<string> capabilities)
        {
            List<string> normalized =
                (capabilities ??
                 Enumerable.Empty<string>())
                .Where(
                    value =>
                        !string.IsNullOrWhiteSpace(
                            value))
                .Select(
                    value =>
                        value.Trim())
                .Distinct(
                    StringComparer.Ordinal)
                .ToList();


            if (!normalized.Contains(
                    "get_editor_selection"))
            {
                normalized.Add(
                    "get_editor_selection");
            }

            if (!normalized.Contains(
                    "inspect_editor_component"))
            {
                normalized.Add(
                    "inspect_editor_component");
            }

            if (!normalized.Contains(
                    "get_hierarchy_tree"))
            {
                normalized.Add(
                    "get_hierarchy_tree");
            }


            return normalized;
        }


        private static IReadOnlyList<string>
            ResolveKanbanCapabilitiesForRun(
                bool coordinationOnly)
        {
            if (coordinationOnly)
            {
                return new[]
                {
                    "get_kanban_task_context",
                    "create_kanban_child_task",
                    "commit_kanban_plan",
                        "save_kanban_checkpoint",
                        "read_kanban_memory"
                };
            }

            return AIRuntimeEditorPluginCatalog
                .GetCapabilityPlugins(
                    AIRuntimeEditorPluginHost.Kanban)
                .Where(plugin =>
                    !string.Equals(
                        plugin.Name,
                        "create_kanban_child_task",
                        StringComparison.Ordinal) &&
                    !string.Equals(
                        plugin.Name,
                        "commit_kanban_plan",
                        StringComparison.Ordinal))
                .Select(plugin => plugin.Name)
                .Distinct(StringComparer.Ordinal)
                .OrderBy(name => name, StringComparer.Ordinal)
                .ToArray();
        }


        private static string BuildSystemPrompt(
            string configuredPrompt,
            bool requiresRootFileList,
            bool requiresFileContentRead,
            bool requiresComponentInspection)
        {
            string prompt =
                configuredPrompt?.Trim() ??
                string.Empty;

            string systemPrompt =
                string.IsNullOrEmpty(
                    prompt)
                    ? TaskSystemPrompt
                    : prompt +
                      "\n\n" +
                      TaskSystemPrompt;

            if (requiresRootFileList)
            {
                systemPrompt +=
                    "\n\n" +
                    RootFileListSystemPrompt;
            }

            if (requiresFileContentRead)
            {
                systemPrompt +=
                    "\n\n" +
                    FileContentSystemPrompt;
            }

            if (requiresComponentInspection)
            {
                systemPrompt +=
                    "\n\n" +
                    ComponentInspectionSystemPrompt;
            }

            return systemPrompt;
        }


        private static string BuildComponentInspectionRetryPrompt(
            string title,
            string effectiveRequest)
        {
            StringBuilder prompt =
                new StringBuilder();

            prompt.AppendLine(
                "Your previous response cannot complete this Kanban task because inspect_editor_component was not called.");

            prompt.AppendLine(
                "Retry the task now. Do not repeat or defend the previous response.");

            prompt.AppendLine();
            prompt.AppendLine(
                "REQUIRED TOOL SEQUENCE:");

            prompt.AppendLine(
                "1. Call get_editor_selection with includeComponents=true.");

            prompt.AppendLine(
                "2. Use only the fresh result to identify the selected GameObject and component. Never invent an instance ID.");

            prompt.AppendLine(
                "3. Call inspect_editor_component. Pass the exact GameObjectInstanceId and ComponentInstanceId when available. If exactly one GameObject is selected and the task asks to inspect all of its components, calling inspect_editor_component with an empty object is valid.");

            prompt.AppendLine(
                "4. Do not provide a final answer until inspect_editor_component completes. Base the answer on its Components and Diagnostics.");

            prompt.AppendLine();
            prompt.AppendLine(
                $"Kanban task: {title?.Trim()}");

            prompt.AppendLine(
                effectiveRequest?.Trim() ??
                string.Empty);

            return prompt.ToString();
        }


        private static string BuildCoordinationTaskPrompt(
            string title,
            string description,
            string acceptanceCriteria,
            string nextAgentMessage)
        {
            StringBuilder prompt =
                new StringBuilder();

            prompt.AppendLine(
                "PLANNING / ORCHESTRATION-ONLY KANBAN RUN: Define the complete implementation graph before any implementation begins.");

            prompt.AppendLine(
                "You are the durable parent orchestrator. You must not inspect, create, edit, compile, test, or verify Unity project assets in this run. AIRuntime has intentionally exposed only scoped Kanban graph capabilities.");

            prompt.AppendLine();
            prompt.AppendLine(
                $"Parent title: {title?.Trim()}");

            prompt.AppendLine(
                "Parent description:");
            prompt.AppendLine(
                description?.Trim() ?? string.Empty);

            prompt.AppendLine();
            prompt.AppendLine(
                "Parent acceptance criteria:");
            prompt.AppendLine(
                acceptanceCriteria?.Trim() ?? string.Empty);

            if (!string.IsNullOrWhiteSpace(nextAgentMessage))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "PLANNING CORRECTION TO APPLY:");
                prompt.AppendLine(
                    nextAgentMessage.Trim());
            }

            prompt.AppendLine();
            prompt.AppendLine(
                "Create the smallest useful dependency graph. Keep a cohesive outcome atomic; split only independent implementation or verification outcomes. Every card created in this run is a direct child of the project root, so produce execution-ready leaves rather than umbrella cards that would need later decomposition. Decide shared architecture, canonical paths, namespaces, coordinate conventions, and test seams once in the parent, then stamp every relevant decision into each self-contained child description.");

            prompt.AppendLine(
                "PLANNING PHASE BOUNDARY: Finish and validate the complete big-picture implementation graph in this run before any implementation card is released. You own graph changes; do not edit product files, scenes, prefabs, tests, or reports. Proposal cards are durable within the current PlanningAttemptId: inspect the embedded context, preserve valid proposals, and create or revise only missing or incorrect cards. On a new PlanningAttemptId, the submitted set is the authoritative graph snapshot and omitted proposal cards may be retired.");

            prompt.AppendLine(
                "EXACT FILE OWNERSHIP: A ProducedArtifactPath naming a folder authorizes that folder only, not files inside it. Do not require a setup card to create placeholder scripts or assets owned by later implementation cards. Give each file one owner responsible for creating and completing it. Put shared architecture documentation in an explicitly owned documentation file; list it as a consumed artifact with a dependency wherever needed. Check each card's acceptance criteria against its exact write authority before committing the graph.");
            prompt.AppendLine(
                "EVIDENCE OWNERSHIP: Source-only cards are reviewed using their declared source outputs and compilation. Assign scene wiring, instantiated component inspection, and integrated gameplay evidence to a scene/integration owner ordered after its source dependencies. Never make a source predecessor wait for its downstream scene as a condition of passing. If a source card explicitly diagnoses an existing scene or prefab, declare that existing artifact as a consumed input; if it must mutate it, declare exact mutation authority. Shared runtime invariants remain requirements on each owner's implementation, with cross-artifact behavior checked by integration.");


            prompt.AppendLine(
                "Use create_kanban_child_task once per owned outcome. Declare exact ProducesArtifactPaths and ConsumesArtifactPaths for durable Assets/... outputs, MutatesResourceKeys for shared scenes or other mutable state, and every prerequisite through DependsOnSiblingTitles. Parallel children must not write the same artifact or mutable resource.");

            prompt.AppendLine(
                "Use only agent IDs listed in the embedded ActiveAgentIds roster. When no active specialist is a better fit, omit AssignedAgent so the child inherits this parent agent.");

            prompt.AppendLine(
                "End the graph with a dedicated final integration-verification child that depends on all required producer and test outcomes, consumes their artifacts, and explicitly verifies every acceptance criterion from the parent. Do not perform that verification in this parent run.");

            prompt.AppendLine(
                "After the graph is complete, call commit_kanban_plan with CommitSequence=1. If it reports an error, correct only the reported graph defects, increment CommitSequence, and call it again. A successful commit is the authoritative completion signal; any prose after it is optional. Children remain draft-blocked until AIRuntime closes the run and releases the committed graph. Do not claim the parent goal itself is complete.");

            return prompt.ToString();
        }


        private static string BuildTaskPrompt(
            string title,
            string description,
            string acceptanceCriteria,
            string previousAgentOutput,
            string nextAgentMessage,
            string effectiveRequest,
            string componentInspectionEvidence)
        {
            StringBuilder prompt =
                new StringBuilder();

            bool hasFollowUp =
                !string.IsNullOrWhiteSpace(
                    nextAgentMessage) &&
                !IsAssociationLeafHandoff(
                    nextAgentMessage);

            bool resumesAssociationLeaf =
                IsAssociationLeafHandoff(
                    nextAgentMessage);

            if (hasFollowUp)
            {
                prompt.AppendLine(
                    "Continue the Kanban task by completing the current active request below.");

                prompt.AppendLine();
                prompt.AppendLine(
                    "CURRENT ACTIVE REQUEST (this replaces the original card request for this run):");

                prompt.AppendLine(
                    nextAgentMessage.Trim());

                prompt.AppendLine();
                prompt.AppendLine(
                    "Do not merely acknowledge or restate the current active request. Perform it and return its result.");
            }
            else
            {
                prompt.AppendLine(
                    "Complete the following Kanban task:");

                prompt.AppendLine();
                prompt.AppendLine(
                    $"Title: {title?.Trim()}");
            }

            if (resumesAssociationLeaf)
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "AIRUNTIME ATOMIC-LEAF HANDOFF: The full original card remains the active request. Complete and verify it directly in this run; the prior coordination turn proved that delegating the same artifact would recurse.");
                prompt.AppendLine(
                    nextAgentMessage.Trim());
            }

            if (RequiresEditorSelection(
                    effectiveRequest) &&
                string.IsNullOrWhiteSpace(
                    componentInspectionEvidence))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY: Call get_editor_selection before answering. Base every object detail only on that fresh tool result. Do not infer or invent names, transforms, components, prefab status, or missing scripts.");

                prompt.AppendLine(
                    "If the selected object has IsFolder=true and the task asks for its files, call list_directory with path set to its exact FolderPath, entryType=\"Files\", outputFormat=\"PathsOnly\", and includeMetaFiles=false. Output only the returned paths unless the task requests another format.");

                prompt.AppendLine(
                    "If the active request only asks you to describe a selected folder, do not inspect or list its contents.");
            }

            if (RequiresRootFileList(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY ROOT FILE LIST: Call list_directory with path=\".\", entryType=\"Files\", and outputFormat=\"PathsOnly\". The final answer must contain every returned path exactly once, one plain path per line, and absolutely no heading, numbering, descriptions, metadata, summary, or offer of more help.");
            }

            if (RequiresDirectoryTree(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY DIRECTORY TREE: Call list_directory_tree using the exact path and bounds requested by the task. When metadata files are excluded, pass includeMetaFiles=false. The final answer must contain every returned Entries[].Path value exactly once, one plain path per line, with no prose or metadata.");
            }

            string verifiedCapability =
                ExtractVerifiedCapabilityName(
                    effectiveRequest);

            if (!string.IsNullOrWhiteSpace(
                    verifiedCapability))
            {
                prompt.AppendLine();

                if (string.Equals(
                        verifiedCapability,
                        "get_kanban_task_context",
                        StringComparison.Ordinal))
                {
                    prompt.AppendLine(
                        "MANDATORY CAPABILITY VERIFICATION: AIRuntime calls get_kanban_task_context immediately before the model run and embeds its returned result below. Use that authoritative result; do not call the capability again.");
                }
                else
                {
                    prompt.AppendLine(
                        $"MANDATORY CAPABILITY VERIFICATION: Call {verifiedCapability} and base the report on its returned result. The capability is available for this run; do not claim that it is missing or unavailable.");
                }
            }

            prompt.AppendLine();
            prompt.AppendLine(
                "IMPLEMENTATION PHASE BOUNDARY: The task graph is already approved and create_kanban_child_task is unavailable. Implement the approved card directly and produce every declared output. A missing method, property, object, component, configuration, or behavior inside this card's ProducedArtifactPaths is an implementation defect to fix now, never a planning defect merely because the current artifact does not yet satisfy its acceptance criteria. Future integration by a downstream card also does not make this card contradictory. Return exactly [PlanningKickback] only for a structural defect that cannot be corrected within this card's owned outputs, such as contradictory acceptance requirements, missing artifact ownership, an actual dependency cycle, or multiple independent outcomes requiring separate ownership. Include the concrete structural evidence, affected artifacts, and suggested graph correction; do not use PlanningKickback to avoid an ordinary implementation edit.");

            prompt.AppendLine(
                "COMPILER FEEDBACK: For compiler errors or API mismatches, read_file may inspect the exact CompletedDependencyArtifactPaths in the embedded Kanban context. Compare those reviewed upstream APIs with the failing owned source and correct this card's implementation. Completed dependency paths grant read access only, never permission to modify another card's artifacts.");

            prompt.AppendLine(
                "CAPABILITY AUTHORITY: Every capability plugin installed for Kanban execution is available in this run, subject to AIRuntime's independent permission policy. Call list_capabilities when discovery is useful, and choose the closest semantic capability; do not redesign a valid task merely to match a smaller historical tool list. An ArtifactOwnership denial is an artifact contract issue, not a missing capability. If the denied artifact is required by this card and no committed owner can provide it, return [PlanningKickback] with the exact path and required ownership/dependency correction; do not call request_capability for that denial. If no installed capability can perform the required operation, call request_capability with the missing operation, complete input/output contract, permission class, and why existing capabilities cannot perform it. Do not fake Unity YAML, claim a product defect, or repeatedly retry adjacent tools. AIRuntime will preserve the structured request and pause the card with Capability Required so the capability can be supplied and the same card resumed.");

            prompt.AppendLine(
                "PERSISTENCE AND EVIDENCE: Treat every observed file version, Unity GUID, serialized collection size, and asset identity as concurrency-sensitive. For a new file use write_file with ExpectedAbsent=true; for an existing file prefer ExpectedSha256 from the latest read before replacement. Use Unity asset lifecycle tools instead of filesystem move/copy/delete for Assets content. Capability Artifacts are authoritative media attachments: inspect the attached content, not merely its path or byte count, before making visual claims.");

            prompt.AppendLine(
                $"UNITY PROJECT VERSION: This workspace runs Unity {Application.unityVersion}. Use only APIs available in this exact version; do not substitute APIs from a newer Unity release.");

            if (RequiresPlayModeTestAuthoring(effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "PLAY MODE TEST AUTHORING PREFLIGHT: Before writing test source, verify that a named runtime asmdef owns the game scripts and that a separate non-Editor-restricted PlayMode test asmdef references that runtime assembly and optionalUnityReferences TestAssemblies. If either assembly definition is absent or inconsistent, stop with a concrete dependency error; do not write a test into Assembly-CSharp and do not guess a GUID. Use the exact runtime component namespace. Do not use UnityEngine.TestTools.Utils.InputHelpers or claim that legacy UnityEngine.Input keys can be synthesized. Exercise the deterministic input/control seam required by the ancestor contract. After writing the source, wait for compilation and call get_compilation_errors; completion requires zero errors from both runtime and test assemblies.");

                prompt.AppendLine(
                    "WRITE SAFETY: write_file content must be the complete non-empty file body. For rework, read the existing source or asmdef first and send a complete replacement; never issue an empty-content write as a placeholder.");
            }

            if (string.Equals(
                    verifiedCapability,
                    "append_file",
                    StringComparison.Ordinal))
            {
                string appendPath =
                    ExtractRequestedFilePath(
                        effectiveRequest);

                string appendContent =
                    ExtractExactAppendContent(
                        effectiveRequest);

                if (!string.IsNullOrWhiteSpace(
                        appendPath) &&
                    appendContent !=
                    null)
                {
                    prompt.AppendLine(
                        $"For append_file, use exactly these argument names and values: {JsonConvert.SerializeObject(new { path = appendPath, content = appendContent })}. The text belongs in the content field, never text, value, data, or an omitted argument.");

                    prompt.AppendLine(
                        "AIRuntime reset the target to the explicit original-text baseline immediately before this run and will independently read and validate the final contents. Call append_file exactly once. Do not call write_file or read_file yourself.");
                }
            }

            if (string.Equals(
                    verifiedCapability,
                    "remove_editor_component",
                    StringComparison.Ordinal))
            {
                string removalArguments =
                    ResolveExactComponentRemovalArguments(
                        effectiveRequest,
                        componentInspectionEvidence);

                if (!string.IsNullOrWhiteSpace(
                        removalArguments))
                {
                    prompt.AppendLine();
                    prompt.AppendLine(
                        $"For remove_editor_component, use exactly these authoritative arguments from the fresh component inspection: {removalArguments}. Do not omit ComponentInstanceId and do not substitute a component type, list index, placeholder, or stale ID. After removal, AIRuntime will perform an independent fresh inspection.");
                }
            }



            if (RequestsAllSelectedMissingScriptRemoval(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY MUTATION: The handoff explicitly requests removal of missing components from the selected objects. After get_editor_selection, call remove_editor_component once with removeMissingScripts=true and removeFromAllSelected=true. Do not invent or reuse an instance ID. Report the returned AffectedCount and AffectedGameObjects.");
            }

            if (RequiresImplementationFileContentRead(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY FILE CONTENT READ: Call read_file before answering. If the active request names only a filename, resolve it relative to the exact selected-folder path stated in the previous agent output. Preserve every '/' directory separator exactly and never replace path separators with underscores. Do not substitute list_directory for read_file. Summarize or explain only the returned file contents.");
            }

            if (effectiveRequest.IndexOf("[AIRUNTIME_COMPONENT_EVIDENCE_POLICY:SKIP]", StringComparison.Ordinal) < 0 &&
                RequiresComponentInspection(effectiveRequest))
            {
                prompt.AppendLine();

                if (string.IsNullOrWhiteSpace(
                        componentInspectionEvidence))
                {
                    prompt.AppendLine(
                        "MANDATORY COMPONENT INSPECTION: After get_editor_selection, call inspect_editor_component before troubleshooting or proposing a fix. Report concrete Error and Warning diagnostics and the inspected settings that support them. If the user requested a supported property fix, call get_serialized_properties and then set_serialized_property using the exact path and expected current value; do not claim a fix occurred unless set_serialized_property returns Verified=true.");
                }
                else
                {
                    prompt.AppendLine(
                        "AIRuntime already completed the mandatory fresh selection and initial inspect_editor_component calls for this run using the real selected GameObject ID. Do not repeat either call before acting and do not substitute an example or placeholder instance ID.");

                    prompt.AppendLine(
                        "Treat the following inspect_editor_component result as authoritative. Base the response only on its Components, Properties, and Diagnostics:");

                    prompt.AppendLine();
                    prompt.AppendLine(
                        componentInspectionEvidence.Trim());

                    prompt.AppendLine();
                    prompt.AppendLine(
                        "Report concrete Error and Warning diagnostics and the inspected settings that support them. If the user requested a supported property fix, use get_serialized_properties and set_serialized_property with exact returned paths and expected values; do not claim a property fix occurred unless set_serialized_property returns Verified=true. If the user explicitly requested a fix and the diagnostic recommends adding a missing component, use add_editor_component with the authoritative GameObjectInstanceId, then call inspect_editor_component again to verify the result. Never claim a component was added unless add_editor_component returns Succeeded=true. For MeshColliderMissingMesh, assign Shared Mesh only when the evidence identifies an authoritative source mesh that can be referenced safely. If no MeshFilter mesh or specific mesh asset is available, do not invent or arbitrarily select one. State that an automatic fix is unsafe and give concrete manual options, including assigning the intended mesh asset, using the matching MeshFilter mesh, or replacing the MeshCollider with an appropriate primitive collider.");

                    if (Regex.IsMatch(
                            effectiveRequest ??
                            string.Empty,
                            @"\bRigidbody\b.{0,80}\bconstraints?\b|\bFreeze\s+(?:Position|Rotation)\b|\bm_Constraints\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant |
                            RegexOptions.Singleline))
                    {
                        prompt.AppendLine();
                        prompt.AppendLine(
                            "RIGIDBODY CONSTRAINT REPORTING: Copy m_Constraints and the decoded FreezePositionX/Y/Z and FreezeRotationX/Y/Z Boolean fields exactly as returned by inspect_editor_component. Those decoded fields are authoritative. Do not manually reinterpret the integer, invent enum bit values, show scratch arithmetic, or contradict the decoded fields.");
                    }
                }
            }

            if (!string.IsNullOrWhiteSpace(
                    description))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    hasFollowUp
                        ? "Original card description (context only; it is not the current request):"
                        : "Description:");

                prompt.AppendLine(
                    description.Trim());
            }

            if (!string.IsNullOrWhiteSpace(
                    acceptanceCriteria))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    hasFollowUp
                        ? "Original acceptance criteria (apply only where relevant to the current request):"
                        : "Acceptance criteria:");

                prompt.AppendLine(
                    acceptanceCriteria.Trim());
            }

            if (!string.IsNullOrWhiteSpace(
                    previousAgentOutput))
            {
                prompt.AppendLine();
                prompt.AppendLine("Previous agent output:");
                prompt.AppendLine(
                    previousAgentOutput.Trim());
            }

            if (hasFollowUp)
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    $"Original card title (context only): {title?.Trim()}");

                prompt.AppendLine();
                prompt.AppendLine(
                    "FINAL REMINDER — answer this current active request:");

                prompt.AppendLine(
                    nextAgentMessage.Trim());
            }

            if (RequiresRootFileList(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "FINAL RESPONSE CONTRACT: Output only every Path returned by list_directory, one exact plain path per line. Any analysis, summary, metadata, heading, bullet, numbering, recommendation, or omitted path makes the task incorrect.");
            }

            return prompt.ToString();
        }


        private static string AppendHierarchyTreeEvidence(
            string prompt,
            string hierarchyTreeEvidence,
            bool reviewMode)
        {
            if (string.IsNullOrWhiteSpace(
                    hierarchyTreeEvidence))
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ??
                    string.Empty);

            result.AppendLine();
            result.AppendLine();
            result.AppendLine(
                "AIRuntime already completed get_editor_selection and get_hierarchy_tree for this run using the real selected GameObject ID.");

            result.AppendLine(
                "Do not call get_hierarchy_tree again. A Unity HierarchyPath or ScenePath is not a filesystem path; do not pass it to list_directory, list_directory_tree, read_file, or any other filesystem tool.");

            result.AppendLine(
                reviewMode
                    ? "Treat this fresh hierarchy result as authoritative and compare the implementation output against it:"
                    : "Treat this hierarchy result as authoritative and base the completion report only on it:");

            result.AppendLine();
            result.AppendLine(
                hierarchyTreeEvidence.Trim());

            return result.ToString();
        }


        private static string AppendKanbanTaskContextEvidence(
            string prompt,
            string evidence,
            bool reviewMode)
        {
            if (string.IsNullOrWhiteSpace(evidence))
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ??
                    string.Empty);

            result.AppendLine();
            result.AppendLine();
            result.AppendLine(
                reviewMode
                    ? "AIRuntime captured this authoritative scoped Kanban card context immediately before review:"
                    : "AIRuntime captured this authoritative scoped Kanban card context for the currently executing task:");
            result.AppendLine();
            result.AppendLine(evidence.Trim());
            result.AppendLine();
            result.AppendLine(
                "AIRuntime already called get_kanban_task_context for this run. Do not call it again. Use this metadata to verify parent attachment, CreatedStatus, assignment inheritance, creation source, and child counts. Do not search the workspace for the external Kanban board and do not infer these facts merely from execution. CreationSource=create_kanban_child_task proves capability creation; AgentAssignmentInherited=true plus AssignmentMatchesParent=true proves inherited assignment.");

            result.AppendLine(
                "INHERITED INTEGRATION CONTRACT: AncestorContracts is authoritative context for this child. Global constraints from every ancestor—such as exact asset locations, coordinate axes or planes, naming, allowed workspace scope, shared references, persistence requirements, and producer-consumer conventions—remain mandatory even when this leaf card does not repeat them. Work owned by other sibling cards is context only and need not be implemented here, but this child must not choose a contradictory convention or mutate a different shared artifact. If the leaf card and an ancestor genuinely conflict, stop rather than silently overriding the ancestor.");

            result.AppendLine(
                reviewMode
                    ? "Review the leaf against its local Description and AcceptanceCriteria, plus inherited integration invariants. SiblingContracts is the authoritative allocation of the parent outcome: do not fail this card for behavior, scripts, prefabs, UI logic, or verification explicitly owned by an unfinished sibling unless this card's own contract also requires that exact deliverable. Parent acceptance criteria are not duplicated onto every child. A locally valid result that uses a conflicting axis, scene path, object identity, reference, data format, or other shared convention must receive FAIL."
                    : "Before mutating a shared asset, confirm that the exact persistent identity and conventions agree with AncestorContracts. Never substitute the currently active scene, selection, or similarly named asset for the inherited target.");

            if (reviewMode)
            {
                try
                {
                    JArray siblings =
                        JObject.Parse(evidence)["SiblingContracts"] as JArray;

                    List<JToken> unfinishedSiblings =
                        (siblings ?? new JArray())
                            .Where(
                                sibling =>
                                    !string.Equals(
                                        sibling?["Status"]?.Value<string>(),
                                        "Done",
                                        StringComparison.Ordinal))
                            .Take(16)
                            .ToList();

                    if (unfinishedSiblings.Count > 0)
                    {
                        result.AppendLine();
                        result.AppendLine(
                            "UNFINISHED SIBLING OWNERSHIP (do not charge these outcomes to the active card):");

                        foreach (JToken sibling in unfinishedSiblings)
                        {
                            string siblingTitle =
                                sibling?["Title"]?.Value<string>() ??
                                "Unnamed sibling";

                            List<string> producedPaths =
                                (sibling?["ProducedArtifactPaths"] as JArray ??
                                 new JArray())
                                    .Values<string>()
                                    .Where(path => !string.IsNullOrWhiteSpace(path))
                                    .ToList();

                            result.Append("- ");
                            result.Append(siblingTitle);

                            if (producedPaths.Count > 0)
                            {
                                result.Append(" owns ");
                                result.Append(string.Join(", ", producedPaths));
                            }

                            result.AppendLine();
                        }

                        result.AppendLine(
                            "These cards are intentionally unfinished. Their behavior is a future integration obligation, not missing work on the active card, unless the active card's own local acceptance criteria explicitly duplicate it.");
                    }
                }
                catch (JsonException)
                {
                    // The raw authoritative context remains available below;
                    // an optional concise summary must never break a review.
                }
            }

            result.AppendLine(
                "For an acceptance criterion saying a parent remains blocked in To Do until its descendants reach Done: DescendantGateState=BlockedInToDo is the correct state while any descendant is incomplete; DescendantGateState=ReleasedAllDescendantsDone with the card in In Progress or Review is the correct state after every descendant completes. Do not fail a released card merely because it is no longer in To Do.");

            result.AppendLine(
                "When the executing card is the spawning parent, verify the spawned child's CreationSource, ParentTaskId, CreatedStatus, AssignedAgent, and AgentAssignmentInherited using the matching DirectChildren entry. Do not require those child values on the parent card itself; a top-level parent's own CreationSource may correctly be empty and its own AgentAssignmentInherited may correctly be false.");

            if (reviewMode)
            {
                result.AppendLine(
                    "When an acceptance criterion checks this executing card's ParentTaskId, CreationSource, or inherited assignment, the authoritative scoped metadata is the required evidence. Do not require the implementation agent to repeat those values in its prose report, and do not fail a supported criterion merely because the report did not restate it.");
            }

            return result.ToString();
        }


        private static string GetEffectiveRequest(
            string title,
            string description,
            string acceptanceCriteria,
            string nextAgentMessage)
        {
            string originalRequest =
                string.Join(
                    "\n",
                    title ?? string.Empty,
                    description ?? string.Empty,
                    acceptanceCriteria ?? string.Empty);

            if (IsAssociationLeafHandoff(nextAgentMessage))
            {
                return originalRequest;
            }

            if (!string.IsNullOrWhiteSpace(
                    nextAgentMessage))
            {
                return nextAgentMessage.Trim();
            }

            return originalRequest;
        }


        private static string GetClassificationRequest(
            string originalCardRequest,
            string effectiveRequest,
            string nextAgentMessage)
        {
            // A review-rework handoff contains the reviewer's evidence prose as
            // well as its corrections. Words such as "read tools" and "files"
            // describe missing review evidence; they do not turn an unrelated
            // scene-authoring card into a file-content request. Keep the full
            // handoff in the model prompt, but classify mandatory tool contracts
            // from the original card.
            if (IsSystemGeneratedReviewHandoff(nextAgentMessage) ||
                IsAssociationLeafHandoff(nextAgentMessage))
            {
                return originalCardRequest ??
                       string.Empty;
            }

            return effectiveRequest ??
                   string.Empty;
        }


        private static bool IsReviewReworkMessage(
            string message)
        {
            if (string.IsNullOrWhiteSpace(message))
                return false;

            return Regex.IsMatch(
                message.TrimStart(),
                @"^(?:\[ReviewFailureKind:\s*\w+\]\s*)?(?:The first review requested rework|Review requested further work)\.",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static bool IsSystemGeneratedReviewHandoff(
            string message)
        {
            return !string.IsNullOrWhiteSpace(message) &&
                   Regex.IsMatch(
                       message.TrimStart(),
                        @"^\[(?:ReviewFailureKind:\s*\w+|OwnershipScopeRecovery|ReviewContextRecovery|AssemblyReloadResume)\]",
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static bool IsAssociationLeafHandoff(
            string message)
        {
            return !string.IsNullOrWhiteSpace(message) &&
                   message.TrimStart().StartsWith(
                       "[AssociationLeaf]",
                       StringComparison.Ordinal);
        }


        private static bool RequiresNewCorrectiveChildren(
            string message)
        {
            if (!IsReviewReworkMessage(message))
                return false;

            Match kind =
                Regex.Match(
                    message ?? string.Empty,
                    @"^\s*\[ReviewFailureKind:\s*(?<kind>\w+)\]",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (!kind.Success)
                return true;

            return !string.Equals(
                kind.Groups["kind"].Value,
                "EvidenceGap",
                StringComparison.OrdinalIgnoreCase);
        }


        private static bool RequiresRunComponentInspection(
            bool reviewMode,
            string classificationRequest,
            string effectiveRequest,
            bool verifiesSetSerializedProperty)
        {
            string request =
                classificationRequest;

            return RequiresComponentInspection(
                       request) ||
                   (reviewMode &&
                    RequiresReviewComponentInspection(
                        request)) ||
                   verifiesSetSerializedProperty;
        }


        private static string GetReviewEffectiveRequest(
            string title,
            string description,
            string acceptanceCriteria,
            string nextAgentMessage)
        {
            return string.Join(
                "\n",
                title ??
                string.Empty,
                description ??
                string.Empty,
                acceptanceCriteria ??
                string.Empty,
                nextAgentMessage ??
                string.Empty);
        }


        private static bool ContainsSingleSelectedGameObject(
            string selectionEvidence)
        {
            try
            {
                ResolveSingleSelectedGameObjectId(
                    selectionEvidence);

                return true;
            }
            catch (InvalidOperationException)
            {
                return false;
            }
        }


        private static GameObject PrepareDisposableAddComponentVerificationTarget()
        {
            GameObject target =
                new GameObject(
                    "AIRuntime Add Component Verification " +
                    Guid.NewGuid()
                        .ToString("N")
                        .Substring(0, 8));

            Undo.RegisterCreatedObjectUndo(
                target,
                "Create AIRuntime component verification target");

            Selection.objects =
                new UnityEngine.Object[]
                {
                    target
                };

            return target;
        }


        private static bool RequiresEditorSelection(
            string request)
        {
            return request.IndexOf(
                       "selected",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "current selection",
                       StringComparison.OrdinalIgnoreCase) >=
                   0;
        }

        private static bool RequiresRootFileList(
            string request)
        {
            bool requestsAllFiles =
                request.IndexOf(
                    "all files",
                    StringComparison.OrdinalIgnoreCase) >=
                0;

            bool requestsRoot =
                request.IndexOf(
                    "root",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                request.IndexOf(
                    "top-level",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                request.IndexOf(
                    "top level",
                    StringComparison.OrdinalIgnoreCase) >=
                0;

            return requestsAllFiles &&
                   requestsRoot;
        }


        private static bool RequiresDirectoryTree(
            string request)
        {
            return
                !string.IsNullOrWhiteSpace(
                    request) &&
                request.IndexOf(
                    "list_directory_tree",
                    StringComparison.OrdinalIgnoreCase) >=
                0;
        }


        private static string ExtractVerifiedCapabilityName(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return null;
            }

            Match match =
                Regex.Match(
                    request,
                    @"verify\s+capability\s*:\s*(?<name>[a-z][a-z0-9_]*)",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            return match.Success
                ? match.Groups["name"]
                    .Value
                    .Trim()
                    .ToLowerInvariant()
                : null;
        }

        private static bool RequiresFileContentRead(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            string normalized =
                RemoveSiblingDependencyClauses(
                    request)
                    .Trim();

            bool namesLikelyFile =
                Regex.IsMatch(
                    normalized,
                    @"(?:\.(?:md|txt|json|cs)\b|\bfiles?\b)",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool requestsContents =
                Regex.IsMatch(
                    normalized,
                    @"\b(?:summari[sz]e|contents|what\s+is|what\s+does|read|explain)\b|\b(?:inspect|review|verify|confirm|check|validate)\b[\s\S]{0,120}\b(?:source|contents?|code|implementation|fields?|directives?|logic|behavio(?:u)?r)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            return namesLikelyFile &&
                   requestsContents;
        }


        private static bool RequiresImplementationFileContentRead(
            string request)
        {
            // "Contents of this card" is a common heading on scene-build
            // tasks. It must not be interpreted as a request to read a file,
            // especially when the scene is the artifact being created.
            if (RequiresSceneAuthoring(request))
                return false;

            if (!RequiresFileContentRead(
                    request))
            {
                return false;
            }

            // A script-authoring card is a mutation task, not a file-summary
            // task. The review pass owns the independent readback. Requiring
            // the implementation model to read its own new file creates an
            // unnecessary terminal failure after the deliverable was written.
            return !Regex.IsMatch(
                RemoveNegatedSceneMutationPhrases(
                    RemoveSiblingDependencyClauses(
                        request)),
                @"\b(?:create|write|author|implement|modify|update|edit|fix)\b.{0,100}(?:\.(?:cs|json|md|txt)\b|\b(?:script|file)\b)|(?:\.(?:cs|json|md|txt)\b|\b(?:script|file)\b).{0,100}\b(?:create|write|author|implement|modify|update|edit|fix)\b",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant |
                RegexOptions.Singleline);
        }


        private static bool RequiresReviewFileEvidence(
            string request)
        {
            string normalized =
                RemoveSiblingDependencyClauses(
                    request);

            return !string.IsNullOrWhiteSpace(
                       normalized) &&
                   Regex.IsMatch(
                       normalized,
                       @"Assets(?:[\\/][A-Za-z0-9_.-]+)+\.(?:cs|json|md|txt)\b",
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static string RemoveSiblingDependencyClauses(
            string request)
        {
            return Regex.Replace(
                request ??
                string.Empty,
                @"Depends\s+on\s+(?:the\s+)?(?:sibling\s+)?tasks?\s+.*?\s+being\s+Done\.?",
                string.Empty,
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant |
                RegexOptions.Singleline);
        }


        private static string RemoveNegatedSceneMutationPhrases(
            string request)
        {
            string normalized =
                request ??
                string.Empty;

            normalized = Regex.Replace(
                normalized,
                @"\b(?:must|should|shall|do|does|did|will|can)\s+not\s+(?:be\s+)?(?:attach(?:ed)?|add(?:ed)?|wir(?:e|ed)|configur(?:e|ed)|assign(?:ed)?|appl(?:y|ied)|set|plac(?:e|ed)|remov(?:e|ed)|touch(?:ed)?|mutat(?:e|ed)|modif(?:y|ied)|edit(?:ed)?|creat(?:e|ed)|build|built)\b[^.;\r\n]*?(?=\bbut\b|\bhowever\b|[.;\r\n]|$)",
                string.Empty,
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);

            normalized = Regex.Replace(
                normalized,
                @"\bnever\s+(?:attach|add|wire|configure|assign|apply|set|place|remove|touch|mutate|modify|edit|create|build)\b[^.;\r\n]*?(?=\bbut\b|\bhowever\b|[.;\r\n]|$)|\bwithout\s+(?:attaching|adding|wiring|configuring|assigning|applying|setting|placing|removing|touching|mutating|modifying|editing|creating|building)\b[^.;\r\n]*?(?=\bbut\b|\bhowever\b|[.;\r\n]|$)",
                string.Empty,
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);

            return Regex.Replace(
                normalized,
                @"\bno\s+(?:scene(?:\s*/\s*GameObject)?|GameObject|prefab|component)\s+(?:mutation|attachment|wiring|configuration)\b",
                string.Empty,
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static string RemoveRuntimeScriptMemberReferences(
            string sentence)
        {
            string normalized =
                sentence ??
                string.Empty;

            // Runtime callback names and member access are common inside a
            // script contract. They are not editor instructions to update a
            // persisted Transform/Rigidbody/scene. Without this normalization,
            // wording such as "in Update, inspect transform.y" satisfies the
            // broad mutation-verb + Unity-target heuristic below.
            normalized = Regex.Replace(
                normalized,
                @"\b(?:in|inside|during|from)\s+(?:Update|FixedUpdate|LateUpdate)\b(?:\s*\(\s*\))?",
                "during the runtime callback",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);

            normalized = Regex.Replace(
                normalized,
                @"\b(?:Update|FixedUpdate|LateUpdate)\s*\(\s*\)",
                "runtime callback",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);

            return Regex.Replace(
                normalized,
                @"\b(?:transform|rigidbody)\s*\.",
                "runtimeMember.",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static bool RequiresReviewCompilationEvidence(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            return Regex.IsMatch(
                request,
                @"\b(?:compile|compiles|compilation|compiler)\b",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static string ScopeCompilationEvidence(
            string rawEvidence,
            string request)
        {
            if (string.IsNullOrWhiteSpace(rawEvidence))
                return rawEvidence;

            JObject raw;

            try
            {
                raw = JObject.Parse(rawEvidence);
            }
            catch (JsonReaderException)
            {
                return rawEvidence;
            }

            List<string> targetPaths =
                Regex.Matches(
                        request ?? string.Empty,
                        @"Assets(?:/[A-Za-z0-9_. -]+)+\.cs\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant)
                    .Cast<Match>()
                    .Select(match => match.Value.Replace('\\', '/'))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();

            bool projectGate =
                targetPaths.Count == 0 ||
                Regex.IsMatch(
                    request ?? string.Empty,
                    @"\b(?:project|all\s+(?:scripts?|assemblies)|entire\s+workspace)\b[\s\S]{0,100}\b(?:compile|compilation|compiler|errors?)\b|\b(?:compile|compilation|compiler)\b[\s\S]{0,100}\b(?:project|all\s+(?:scripts?|assemblies)|zero\s+errors\s+across)\b|\bget_compilation_errors\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            raw["Scope"] =
                projectGate
                    ? "Project"
                    : "TargetArtifacts";

            raw["TargetArtifactPaths"] =
                new JArray(targetPaths);

            if (projectGate)
            {
                raw["ResponseInstruction"] =
                    "This is a project-wide compilation gate. Any Error diagnostic may fail the card.";
                return raw.ToString(Formatting.Indented);
            }

            List<JToken> messages =
                (raw["Messages"] as JArray ?? new JArray())
                    .ToList();

            Func<JToken, bool> belongsToTarget =
                message =>
                {
                    string file =
                        message?["File"]?.Value<string>()
                            ?.Replace('\\', '/') ??
                        string.Empty;

                    return targetPaths.Any(
                        target =>
                            string.Equals(
                                file,
                                target,
                                StringComparison.OrdinalIgnoreCase));
                };

            List<JToken> relevant =
                messages.Where(belongsToTarget).ToList();

            List<JToken> unrelated =
                messages.Where(message => !belongsToTarget(message)).ToList();

            int projectErrorCount =
                raw["ErrorCount"]?.Value<int>() ?? 0;

            int projectWarningCount =
                raw["WarningCount"]?.Value<int>() ?? 0;

            int relevantErrorCount =
                relevant.Count(
                    message =>
                        string.Equals(
                            message?["Type"]?.Value<string>(),
                            "Error",
                            StringComparison.OrdinalIgnoreCase));

            int relevantWarningCount =
                relevant.Count - relevantErrorCount;

            raw["ProjectErrorCount"] = projectErrorCount;
            raw["ProjectWarningCount"] = projectWarningCount;
            raw["ErrorCount"] = relevantErrorCount;
            raw["WarningCount"] = relevantWarningCount;
            raw["MessageCount"] = relevant.Count;
            raw["Messages"] = new JArray(relevant);
            raw["UnrelatedErrorCount"] =
                unrelated.Count(
                    message =>
                        string.Equals(
                            message?["Type"]?.Value<string>(),
                            "Error",
                            StringComparison.OrdinalIgnoreCase));
            raw["UnrelatedDiagnostics"] = new JArray(unrelated);
            raw["TargetArtifactsCompile"] =
                raw["IsCompiling"]?.Value<bool>() != true &&
                relevantErrorCount == 0;
            raw["ResponseInstruction"] =
                "This evidence is scoped to TargetArtifactPaths. Judge this card only by ErrorCount and Messages, which contain diagnostics owned by those artifacts. UnrelatedDiagnostics are upstream context and must not fail this card. Project-wide compilation belongs to a separate integration gate. A false project-wide claim in the implementation report is non-blocking when every card criterion and target artifact passes.";

            return raw.ToString(Formatting.Indented);
        }


        private static bool HasTargetArtifactCompilationErrors(
            string compilationEvidence)
        {
            if (string.IsNullOrWhiteSpace(compilationEvidence))
                return false;

            try
            {
                JObject evidence = JObject.Parse(compilationEvidence);

                return string.Equals(
                           evidence["Scope"]?.Value<string>(),
                           "TargetArtifacts",
                           StringComparison.Ordinal) &&
                       evidence["IsCompiling"]?.Value<bool>() != true &&
                       (evidence["ErrorCount"]?.Value<int>() ?? 0) > 0;
            }
            catch (JsonReaderException)
            {
                return false;
            }
        }


        private static string BuildTargetCompilationPreflightFailure(
            string compilationEvidence)
        {
            JObject evidence = JObject.Parse(compilationEvidence);

            string diagnostics =
                string.Join(
                    "\n",
                    (evidence["Messages"] as JArray ?? new JArray())
                        .OfType<JObject>()
                        .Select(
                            message =>
                                $"- {message["File"]?.Value<string>()}({message["Line"]?.Value<int>()},{message["Column"]?.Value<int>()}): {message["Message"]?.Value<string>()}"));

            return
                "KANBAN_REVIEW: FAIL\n\n" +
                "AIRuntime deterministic pre-review compilation preflight found errors in this card's owned artifacts. No reviewer inference was used. Repair these exact diagnostics in the same card, using APIs compatible with Unity " +
                Application.unityVersion +
                ":\n" +
                diagnostics;
        }


        private static bool RequiresMaterialAssetReviewEvidence(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
            {
                return false;
            }

            return ExtractRequestedMaterialAssetPaths(request).Count > 0;
        }


        private static bool IsStandaloneMaterialAssetReview(
            bool reviewMode,
            string request)
        {
            if (!reviewMode ||
                !RequiresMaterialAssetReviewEvidence(request))
            {
                return false;
            }

            string normalized =
                RemoveSiblingDependencyClauses(request);

            // A material deliverable is a persistent Unity asset, not a
            // component. A producer card may name the future scene or object
            // which consumes the material without owning that scene mutation.
            // Only an imperative mutation in the same sentence makes this a
            // combined material-and-scene card.
            return !SplitReviewContractSentences(normalized)
                .Any(IsSceneOrComponentMutationSentence);
        }


        private static bool RequiresSceneInspectionEvidence(
            bool reviewMode,
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return false;

            if (request.IndexOf(
                    "inspect_scene",
                    StringComparison.OrdinalIgnoreCase) >= 0)
            {
                return true;
            }

            if (!reviewMode)
                return false;

            string normalized =
                RemoveSiblingDependencyClauses(request);

            return SplitReviewContractSentences(normalized)
                .Where(
                    sentence =>
                        sentence.IndexOf(
                            ".unity",
                            StringComparison.OrdinalIgnoreCase) >= 0 ||
                        Regex.IsMatch(
                            sentence,
                            @"\bscene\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant))
                .Any(
                    sentence =>
                        IsSceneOrComponentMutationSentence(sentence) ||
                        Regex.IsMatch(
                            sentence,
                            @"\bscene\b[\s\S]{0,120}\b(?:exists?|contains?|includes?|has|saved|persisted|loaded|reloaded|verified)\b|\b(?:saved|persisted|loaded|reloaded|verified)\b[\s\S]{0,120}\bscene\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant));
        }


        private static IEnumerable<string> SplitReviewContractSentences(
            string request)
        {
            return Regex.Split(
                    request ?? string.Empty,
                    @"(?<=[.!?])\s+|\r?\n",
                    RegexOptions.CultureInvariant)
                .Select(sentence => sentence.Trim())
                .Where(sentence => !string.IsNullOrWhiteSpace(sentence));
        }


        private static bool IsSceneOrComponentMutationSentence(
            string sentence)
        {
            if (string.IsNullOrWhiteSpace(sentence))
                return false;

            sentence =
                RemoveRuntimeScriptMemberReferences(
                    RemoveNegatedSceneMutationPhrases(
                        sentence));

            if (string.IsNullOrWhiteSpace(sentence))
                return false;

            const string mutationVerb =
                @"\b(?:create|build|configure|modify|update|edit|attach|assign|apply|set|wire|add|place|remove|touch)\b";

            const string sceneOrComponentTarget =
                @"\b(?:GameObject|prefab|scene|Collider|Rigidbody|Renderer|MeshFilter|Transform|Camera|Light|component)\b";

            const string authoredAssetPath =
                @"Assets(?:[\\/][A-Za-z0-9_. -]+)+\.(?:mat|cs|asmdef)\b";

            bool authorsStandaloneFile =
                Regex.IsMatch(
                    sentence,
                    @"\b(?:create|write|author|implement)\b[\s\S]{0,160}" +
                    authoredAssetPath,
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool authorsAdditionalPersistedUnityOutcome =
                Regex.IsMatch(
                    sentence,
                    authoredAssetPath +
                    @"[\s\S]{0,160}(?:,\s*|\band\s+)(?:create|build|configure|modify|update|edit|add|place)?\s*(?:an?\s+|the\s+)?(?:playable\s+)?(?:scene|prefab|GameObject|Canvas|HUD|UI|menu)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant) ||
                Regex.IsMatch(
                    sentence,
                    @"\b(?:scene|prefab|GameObject|Canvas|HUD|UI|menu)\b[\s\S]{0,160}\band\b[\s\S]{0,80}" +
                    authoredAssetPath,
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool explicitlyWiresFileToScene =
                Regex.IsMatch(
                    sentence,
                    @"\b(?:attach|add|wire|configure|assign|apply|set|place|remove)\b[\s\S]{0,160}" + sceneOrComponentTarget,
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant) ||
                Regex.IsMatch(
                    sentence,
                    sceneOrComponentTarget + @"[\s\S]{0,160}\b(?:attach|add|wire|configure|assign|apply|set|place|remove)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (authorsAdditionalPersistedUnityOutcome)
                return true;

            if (authorsStandaloneFile &&
                !explicitlyWiresFileToScene)
            {
                return false;
            }

            return Regex.IsMatch(
                       sentence,
                       mutationVerb + @"[\s\S]{0,160}" + sceneOrComponentTarget,
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant) ||
                   Regex.IsMatch(
                       sentence,
                       sceneOrComponentTarget + @"[\s\S]{0,160}" + mutationVerb,
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static IReadOnlyList<string> ResolveOwnedSourceReviewPaths(
            IEnumerable<string> producedPaths, string workspaceRoot)
        {
            var paths = (producedPaths ?? Array.Empty<string>()).Where(path => !string.IsNullOrWhiteSpace(path))
                .Select(path => path.Trim().Replace('\\', '/')).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
            AppendOwningAssemblyDefinitionPaths(paths, workspaceRoot);
            return paths;
        }

        internal static bool IsSourceArtifactContract(
            IEnumerable<string> producedPaths, IEnumerable<string> resourceKeys)
        {
            string[] paths = (producedPaths ?? Array.Empty<string>())
                .Where(path => !string.IsNullOrWhiteSpace(path)).ToArray();
            return paths.Length > 0 &&
                !(resourceKeys ?? Array.Empty<string>()).Any(key => !string.IsNullOrWhiteSpace(key)) &&
                paths.All(path => new[] { ".cs", ".asmdef", ".asmref" }.Any(extension =>
                    path.Trim().EndsWith(extension, StringComparison.OrdinalIgnoreCase)));
        }

        internal static bool IsSourceArtifactEvidenceScope(IEnumerable<string> produced,
            IEnumerable<string> consumed, IEnumerable<string> resources)
        {
            return IsSourceArtifactContract(produced, resources) &&
                !(consumed ?? Array.Empty<string>()).Any(path => !string.IsNullOrWhiteSpace(path) &&
                    (path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase) ||
                     path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)));
        }

        private static bool IsSourceArtifactContext(string context)
        {
            return IsStructuredChildTask(context) && IsSourceArtifactEvidenceScope(
                ExtractStructuredProducedArtifactPaths(context), ExtractStructuredReviewArtifactPaths(context),
                ExtractStructuredMutatedResourceKeys(context));
        }

        private static string BuildOwnershipScopePrompt(string context)
        {
            if (!IsSourceArtifactContext(context)) return string.Empty;
            return "AUTHORITATIVE SOURCE ARTIFACT SCOPE: This card owns source assets, with no live scene or prefab mutation contract. " +
                "Implement and inspect every owned source artifact and verify current compilation. Runtime behavior described by this card " +
                "is a source implementation requirement; it does not require creating a scene, attaching components, assigning Inspector references, " +
                "or demonstrating the assembled game on this card. Scene wiring and integrated runtime evidence belong to their declared owners. " +
                "Do not demand live component evidence or fail because a downstream scene has not been built. " +
                "Still report actual source defects, incompatible APIs, and compilation errors. Explicit contradictory delivery requirements " +
                "must be reported as a PlanningDefect with the exact criterion and artifact; never silently broaden permissions.";
        }

        private static bool IsStandaloneScriptAssetReview(
            bool reviewMode,
            string request)
        {
            return reviewMode &&
                   IsStandaloneScriptAssetTask(request);
        }


        internal static bool IsStandaloneScriptAssetTask(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return false;

            string normalized =
                RemoveNegatedSceneMutationPhrases(
                    RemoveSiblingDependencyClauses(
                        request));

            bool namesLiveUnityTarget =
                Regex.IsMatch(
                    normalized,
                    @"\b(?:GameObject|scene|prefab|selected|current\s+selection|Inspector)\b|\b(?:inspect_editor_component|inspect_game_object)\b|\b(?:inspect|verify|confirm|check)\s+(?:the\s+)?[A-Z][A-Za-z0-9_.-]*\s+(?:Collider|Rigidbody|Renderer|MeshFilter|Transform)\b|\b[A-Z][A-Za-z0-9_.-]*\s+(?:has|contains|carries)\s+(?:an?\s+|the\s+)?(?:Collider|Rigidbody|Renderer|MeshFilter|Transform|[A-Z][A-Za-z0-9_]*)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool authorsExplicitScript =
                Regex.IsMatch(
                    normalized,
                    @"\b(?:create|write|author|implement|modify|update|edit|fix)\b[\s\S]{0,200}?Assets(?:[\\/][A-Za-z0-9_. -]+)+\.cs\b|Assets(?:[\\/][A-Za-z0-9_. -]+)+\.cs\b[\s\S]{0,200}?\b(?:create|write|author|implement|modify|update|edit|fix)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool explicitlyInspectsLiveTarget =
                SplitReviewContractSentences(normalized)
                    .Any(
                        sentence =>
                            Regex.IsMatch(
                                sentence,
                                @"\b(?:inspect_editor_component|inspect_game_object|get_editor_selection)\b|\b(?:inspect|verify|confirm|check|validate)\b[\s\S]{0,120}\b(?:selected|current\s+selection|Inspector|GameObject|scene|prefab|Collider|Rigidbody|Renderer|MeshFilter|Transform|component)\b",
                                RegexOptions.IgnoreCase |
                                RegexOptions.CultureInvariant));

            bool verifiesExplicitScriptSource =
                Regex.IsMatch(
                    normalized,
                    @"Assets(?:[\\/][A-Za-z0-9_. -]+)+\.cs\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant) &&
                Regex.IsMatch(
                    normalized,
                    @"\b(?:verify|verification|confirm|inspect|review|validate|check)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant) &&
                !namesLiveUnityTarget &&
                !Regex.IsMatch(
                    normalized,
                    @"\b(?:attached|enabled|configured)\b[\s\S]{0,100}\b(?:component|Collider|Rigidbody|Renderer|MeshFilter|Transform)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (!authorsExplicitScript &&
                !verifiesExplicitScriptSource)
            {
                return false;
            }

            int distinctScriptPathCount =
                Regex.Matches(
                        normalized,
                        @"Assets(?:[\\/][A-Za-z0-9_. -]+)+\.cs\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant)
                    .Cast<Match>()
                    .Select(
                        match =>
                            match.Value.Replace('\\', '/'))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .Count();

            // One script is already a leaf. Multiple explicitly named
            // scripts are separate durable outcomes and must still reach the
            // decomposition detector below.
            if (distinctScriptPathCount != 1)
                return false;

            if (authorsExplicitScript)
            {
                // A one-file script card stays atomic even when its API
                // contract mentions Rigidbody, Transform, Inspector, scenes,
                // or prefabs as context. Only an imperative persisted-scene
                // mutation or an explicit live inspection makes it a combined
                // outcome that should be decomposed. This prevents an author
                // card from recursively spawning another author for the exact
                // same script merely because its contract says not to touch a
                // scene or describes the runtime component API.
                return !explicitlyInspectsLiveTarget &&
                       !SplitReviewContractSentences(normalized)
                           .Any(IsSceneOrComponentMutationSentence);
            }

            // Script implementation and scene wiring are separate leaf
            // deliverables. Only keep live component inspection on a card
            // which explicitly asks to mutate or verify a persisted scene or
            // prefab target as part of this same leaf.
            return !namesLiveUnityTarget &&
                   !Regex.IsMatch(
                       normalized,
                       @"\b(?:attach|add|wire|configure|assign|apply)\b[\s\S]{0,120}\b(?:GameObject|scene|prefab|component)\b|Assets(?:[\\/][A-Za-z0-9_.-]+)+\.(?:unity|prefab)\b",
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static IReadOnlyList<string> ExtractExplicitMaterialAssetPaths(
            string request)
        {
            return ExtractExplicitAssetPaths(request)
                .Where(path =>
                    path.EndsWith(
                        ".mat",
                        StringComparison.OrdinalIgnoreCase))
                .ToList();
        }


        private static IReadOnlyList<string> ExtractRequestedMaterialAssetPaths(
            string request)
        {
            var paths =
                ExtractExplicitMaterialAssetPaths(request)
                    .ToList();

            if (string.IsNullOrWhiteSpace(request))
                return paths;

            Match directoryMatch =
                Regex.Match(
                    request,
                    @"(?<path>Assets(?:[\\/][A-Za-z0-9_.-]+)+[\\/]Materials)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (!directoryMatch.Success)
                return paths;

            string directory =
                directoryMatch.Groups["path"].Value
                    .Replace('\\', '/')
                    .TrimEnd('/');

            foreach (Match nameMatch in Regex.Matches(
                         request,
                         @"\b(?<name>[A-Za-z_][A-Za-z0-9_]*Material)\b",
                         RegexOptions.IgnoreCase |
                         RegexOptions.CultureInvariant))
            {
                string name =
                    nameMatch.Groups["name"].Value;

                if (string.Equals(
                        name,
                        "Material",
                        StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                string inferredPath =
                    directory + "/" + name + ".mat";

                if (!paths.Contains(
                        inferredPath,
                        StringComparer.OrdinalIgnoreCase))
                {
                    paths.Add(inferredPath);
                }
            }

            return paths;
        }


        private static TimeSpan ResolveKanbanRunTimeout()
        {
            // Kanban work can legitimately span many sequential provider and
            // capability round trips. Its lifetime is governed by provider
            // inactivity, caller cancellation, capability failures, and loop
            // guards—not by a fixed wall-clock duration.
            return Timeout.InfiniteTimeSpan;
        }


        private static async Task<string> GetKanbanTaskContextAsync(
            RuntimeFacade runtime,
            CancellationToken cancellationToken)
        {
            return await runtime.ExecuteCapabilityAsync(
                    "get_kanban_task_context",
                    "{}",
                    cancellationToken);
        }


        private static int GetDirectChildCount(
            string contextJson)
        {
            if (string.IsNullOrWhiteSpace(contextJson))
                return 0;

            return JObject.Parse(contextJson)
                       ["DirectChildCount"]?.Value<int>() ??
                   0;
        }


        private static bool GetAssociationResolvedAsAtomicLeaf(
            string contextJson)
        {
            return !string.IsNullOrWhiteSpace(contextJson) &&
                   (JObject.Parse(contextJson)
                        ["AssociationResolvedAsAtomicLeaf"]?.Value<bool>() ??
                    false);
        }


        private static async Task VerifyDelegationSafetyAsync(
            RuntimeFacade runtime,
            CancellationToken cancellationToken)
        {
            string contextJson =
                await runtime.ExecuteCapabilityAsync(
                    "get_kanban_task_context",
                    "{}",
                    cancellationToken);

            JObject context =
                JObject.Parse(
                    contextJson ??
                    "{}");

            try
            {
                // Voluntary delegation is gated only by deterministic graph
                // integrity. Coverage and card granularity belong to the
                // model and the normal review cycle.
                ValidateDecompositionSafetyGraph(context);


                // The current Planning attempt is an authoritative snapshot.
                // Omitted proposal cards are reconciled by the board after the
                // run, so a smaller desired graph is valid structural progress.
            }
            catch (InvalidOperationException exception)
                when (!exception.Message.TrimStart().StartsWith(
                    "Granular decomposition",
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException(
                    "Delegation validation failed: " +
                    exception.Message,
                    exception);
            }
        }


        private static void ValidateDecompositionSafetyGraph(
            JObject context)
        {
            List<JObject> children =
                (context?["DirectChildren"] as JArray ?? new JArray())
                    .OfType<JObject>()
                    .ToList();

            string[] duplicateTitles =
                children
                    .Select(
                        child =>
                            child["Title"]?.Value<string>()?.Trim() ??
                            string.Empty)
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .GroupBy(title => title, StringComparer.OrdinalIgnoreCase)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key)
                    .ToArray();

            if (duplicateTitles.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains duplicate sibling titles: " +
                    string.Join(", ", duplicateTitles) +
                    ". Use unique titles so dependencies remain unambiguous.");
            }

            string[] unresolvedDependencies =
                children
                    .SelectMany(
                        child =>
                            (child["UnresolvedDependencyTitles"] as JArray ??
                             new JArray()).Values<string>())
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (unresolvedDependencies.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains unresolved sibling dependencies: " +
                    string.Join(", ", unresolvedDependencies) +
                    ". Correct the exact sibling titles before implementation starts.");
            }

            Dictionary<string, JObject> byId =
                children
                    .Where(
                        child =>
                            !string.IsNullOrWhiteSpace(
                                child["TaskId"]?.Value<string>()))
                    .ToDictionary(
                        child => child["TaskId"].Value<string>(),
                        StringComparer.Ordinal);

            string[] missingDependencyIds =
                children
                    .SelectMany(
                        child =>
                            (child["DependsOnTaskIds"] as JArray ??
                             new JArray()).Values<string>())
                    .Where(
                        dependencyId =>
                            !string.IsNullOrWhiteSpace(dependencyId) &&
                            !byId.ContainsKey(dependencyId))
                    .Distinct(StringComparer.Ordinal)
                    .ToArray();

            if (missingDependencyIds.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition references missing sibling task IDs: " +
                    string.Join(", ", missingDependencyIds) +
                    ". Recreate the affected dependency declarations.");
            }

            string cycle =
                FindDependencyCycle(children, byId);

            if (!string.IsNullOrWhiteSpace(cycle))
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains a dependency cycle: " +
                    cycle +
                    ". Repair the graph before implementation starts.");
            }
        }


        private static void ValidateDecompositionGraph(
            JObject context,
            string parentRequest)
        {
            List<JObject> children =
                (context?["DirectChildren"] as JArray ?? new JArray())
                    .OfType<JObject>()
                    .ToList();

            Func<JObject, string> text =
                child =>
                    string.Join(
                        "\n",
                        child["Title"]?.Value<string>() ?? string.Empty,
                        child["Description"]?.Value<string>() ?? string.Empty,
                        child["AcceptanceCriteria"]?.Value<string>() ?? string.Empty);

            string[] duplicateTitles =
                children
                    .Select(child => child["Title"]?.Value<string>()?.Trim() ?? string.Empty)
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .GroupBy(title => title, StringComparer.OrdinalIgnoreCase)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key)
                    .ToArray();

            if (duplicateTitles.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains duplicate sibling titles: " +
                    string.Join(", ", duplicateTitles) +
                    ". Use unique titles so dependencies remain unambiguous.");
            }

            string[] unresolvedDependencies =
                children
                    .SelectMany(
                        child =>
                            (child["UnresolvedDependencyTitles"] as JArray ?? new JArray())
                                .Values<string>())
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (unresolvedDependencies.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains unresolved sibling dependencies: " +
                    string.Join(", ", unresolvedDependencies) +
                    ". Correct the exact sibling titles before implementation starts.");
            }

            foreach (JObject child in children)
            {
                string childText = text(child);
                string withoutCanonicalDependencies =
                    Regex.Replace(
                        childText,
                        @"Depends\s+on\s+(?:the\s+)?(?:sibling\s+)?tasks?\s+(?:[\""'](?:[^\""']+)[\""'](?:\s*(?:,|and)\s*)?)+\s+being\s+Done\.?",
                        string.Empty,
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                if (Regex.IsMatch(
                        withoutCanonicalDependencies,
                        @"\bdepends\s+on\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant) &&
                    ((child["DependsOnTaskIds"] as JArray)?.Count ?? 0) == 0)
                {
                    throw new InvalidOperationException(
                        $"Granular decomposition contains an informal dependency without any structured prerequisite in '{child["Title"]?.Value<string>()}'. Declare every prerequisite through DependsOnSiblingTitles instead of prose.");
                }
            }

            Dictionary<string, JObject> byId =
                children
                    .Where(child => !string.IsNullOrWhiteSpace(child["TaskId"]?.Value<string>()))
                    .ToDictionary(
                        child => child["TaskId"].Value<string>(),
                        StringComparer.Ordinal);

            string[] missingDependencyIds =
                children
                    .SelectMany(
                        child =>
                            (child["DependsOnTaskIds"] as JArray ?? new JArray())
                                .Values<string>())
                    .Where(
                        dependencyId =>
                            !string.IsNullOrWhiteSpace(dependencyId) &&
                            !byId.ContainsKey(dependencyId))
                    .Distinct(StringComparer.Ordinal)
                    .ToArray();

            if (missingDependencyIds.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition references missing sibling task IDs: " +
                    string.Join(", ", missingDependencyIds) +
                    ". Recreate the affected dependency declarations.");
            }

            string cycle =
                FindDependencyCycle(
                    children,
                    byId);

            if (!string.IsNullOrWhiteSpace(cycle))
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains a dependency cycle: " +
                    cycle +
                    ". Repair the graph before implementation starts.");
            }

            ValidateStructuredArtifactGraph(
                children,
                byId);

            List<JObject> finalVerifiers =
                children
                    .Where(
                        child =>
                            Regex.IsMatch(
                                text(child),
                                @"\bfinal\b[\s\S]{0,80}\b(?:integrated|integration|verification)\b",
                                RegexOptions.IgnoreCase |
                                RegexOptions.CultureInvariant))
                    .ToList();

            List<JObject> verificationGates =
                children
                    .Where(
                        child =>
                            !finalVerifiers.Contains(child) &&
                            Regex.IsMatch(
                                child["Title"]?.Value<string>() ?? string.Empty,
                                @"\b(?:verify|verification|compile|compilation|test)\b",
                                RegexOptions.IgnoreCase |
                                RegexOptions.CultureInvariant))
                    .ToList();

            foreach (JObject finalVerifier in finalVerifiers)
            {
                string[] missingGates =
                    verificationGates
                        .Where(
                            gate =>
                                !DependsTransitively(
                                    finalVerifier,
                                    gate,
                                    byId))
                        .Select(gate => gate["Title"]?.Value<string>() ?? string.Empty)
                        .ToArray();

                if (missingGates.Length > 0)
                {
                    throw new InvalidOperationException(
                        $"Final verification '{finalVerifier["Title"]?.Value<string>()}' does not depend on every verification gate: " +
                        string.Join(", ", missingGates) +
                        ". Declare all gates through DependsOnSiblingTitles.");
                }
            }

            List<JObject> sceneAuthors =
                children
                    .Where(
                        child =>
                            !IsStructuredScriptOnlyChild(child) &&
                            SplitReviewContractSentences(text(child))
                                .Any(IsSceneOrComponentMutationSentence))
                    .ToList();

            bool requiresSeparateSceneSteps =
                Regex.IsMatch(
                    parentRequest ?? string.Empty,
                    @"\bseparate\s+cards?\b[\s\S]{0,160}\beach\s+scene[- ]construction(?:/configuration)?\s+step\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (requiresSeparateSceneSteps)
            {
                JObject combinedSceneCard =
                    sceneAuthors.FirstOrDefault(
                        child =>
                            !OwnsSingleTransactionalSceneResource(
                                child) &&
                            CountIndependentSceneMutationClauses(
                                text(child)) > 1);

                if (combinedSceneCard != null)
                {
                    throw new InvalidOperationException(
                        $"Granular decomposition combines multiple semantic scene-construction/configuration steps in '{combinedSceneCard["Title"]?.Value<string>()}', but the parent requires a separate card for each independently executable scene mutation. Split by owned scene outcome, regardless of numbering or prose format.");
                }
            }

            foreach (JObject producer in children)
            {
                string producerText = text(producer);
                string[] producedScriptPaths =
                    (producer["ProducedArtifactPaths"] as JArray ?? new JArray())
                        .Values<string>()
                        .Where(
                            path =>
                                !string.IsNullOrWhiteSpace(path) &&
                                path.EndsWith(
                                    ".cs",
                                    StringComparison.OrdinalIgnoreCase))
                        .ToArray();

                if (HasStructuredOwnershipContract(producer) &&
                    producedScriptPaths.Length == 0)
                {
                    continue;
                }

                Match scriptPath =
                    Regex.Match(
                        producedScriptPaths.FirstOrDefault() ?? producerText,
                        @"Assets(?:/[A-Za-z0-9_.-]+)+/(?<name>[A-Za-z_][A-Za-z0-9_]*)\.cs\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                if (!scriptPath.Success ||
                    producerText.IndexOf(
                        "MonoBehaviour",
                        StringComparison.OrdinalIgnoreCase) < 0)
                {
                    continue;
                }

                string scriptName =
                    scriptPath.Groups["name"].Value;

                string producedScriptPath =
                    producedScriptPaths.FirstOrDefault();

                List<JObject> consumers =
                    sceneAuthors
                        .Where(
                            child =>
                                !ReferenceEquals(child, producer) &&
                                (!string.IsNullOrWhiteSpace(producedScriptPath)
                                    ? (child["ConsumedArtifactPaths"] as JArray ?? new JArray())
                                        .Values<string>()
                                        .Any(
                                            path =>
                                                string.Equals(
                                                    path,
                                                    producedScriptPath,
                                                    StringComparison.OrdinalIgnoreCase))
                                    : Regex.IsMatch(
                                        text(child),
                                        @"\b" + Regex.Escape(scriptName) + @"\b",
                                        RegexOptions.IgnoreCase |
                                        RegexOptions.CultureInvariant)))
                        .ToList();

                if (consumers.Count == 0)
                {
                    // A script-only decomposition is valid on its own. The
                    // producer/consumer invariant applies only after this
                    // batch actually contains scene-authoring work; otherwise
                    // the validator invents a scene integration requirement
                    // that the parent may not have requested.
                    if (sceneAuthors.Count > 0)
                    {
                        throw new InvalidOperationException(
                            $"Granular decomposition creates MonoBehaviour '{scriptName}' but no scene-authoring child consumes it.");
                    }

                    continue;
                }

                string[] unsafeConsumers =
                    consumers
                        .Where(
                            consumer =>
                                !DependsTransitively(
                                    consumer,
                                    producer,
                                    byId))
                        .Select(consumer => consumer["Title"]?.Value<string>() ?? string.Empty)
                        .ToArray();

                if (unsafeConsumers.Length > 0)
                {
                    throw new InvalidOperationException(
                        $"Scene consumer(s) for MonoBehaviour '{scriptName}' do not depend on producer '{producer["Title"]?.Value<string>()}': " +
                        string.Join(", ", unsafeConsumers) +
                        ". Declare the producer through DependsOnSiblingTitles.");
                }
            }

            foreach (JObject materialProducer in children.Where(
                         child =>
                             (child["ProducedArtifactPaths"] as JArray ?? new JArray())
                                 .Values<string>()
                                 .Any(
                                     path =>
                                         !string.IsNullOrWhiteSpace(path) &&
                                         path.EndsWith(
                                             ".mat",
                                             StringComparison.OrdinalIgnoreCase)) ||
                             (!HasStructuredOwnershipContract(child) &&
                              Regex.IsMatch(
                                  text(child),
                                  @"\b(?:create|author|write)\b[\s\S]{0,120}\bmaterials?\b|Assets(?:/[A-Za-z0-9_.-]+)+\.mat\b",
                                  RegexOptions.IgnoreCase |
                                  RegexOptions.CultureInvariant))))
            {
                string[] producedMaterialPaths =
                    (materialProducer["ProducedArtifactPaths"] as JArray ?? new JArray())
                        .Values<string>()
                        .Where(
                            path =>
                                !string.IsNullOrWhiteSpace(path) &&
                                path.EndsWith(
                                    ".mat",
                                    StringComparison.OrdinalIgnoreCase))
                        .Distinct(StringComparer.OrdinalIgnoreCase)
                        .ToArray();

                List<JObject> materialConsumers =
                    sceneAuthors
                        .Where(
                            child =>
                                !ReferenceEquals(child, materialProducer) &&
                                (producedMaterialPaths.Length > 0
                                    ? (child["ConsumedArtifactPaths"] as JArray ?? new JArray())
                                        .Values<string>()
                                        .Any(
                                            consumedPath =>
                                                producedMaterialPaths.Contains(
                                                    consumedPath,
                                                    StringComparer.OrdinalIgnoreCase))
                                    : Regex.IsMatch(
                                        text(child),
                                        @"\bmaterials?\b",
                                        RegexOptions.IgnoreCase |
                                        RegexOptions.CultureInvariant)))
                        .ToList();

                string[] unsafeMaterialConsumers =
                    materialConsumers
                        .Where(
                            consumer =>
                                !DependsTransitively(
                                    consumer,
                                    materialProducer,
                                    byId))
                        .Select(consumer => consumer["Title"]?.Value<string>() ?? string.Empty)
                        .ToArray();

                if (unsafeMaterialConsumers.Length > 0)
                {
                    throw new InvalidOperationException(
                        $"Scene material consumer(s) do not depend on producer '{materialProducer["Title"]?.Value<string>()}': " +
                        string.Join(", ", unsafeMaterialConsumers) +
                        ". Declare the producer through DependsOnSiblingTitles.");
                }
            }
        }


        private static int CountIndependentSceneMutationClauses(
            string request)
        {
            string normalized =
                RemoveNegatedSceneMutationPhrases(
                    request ?? string.Empty);

            return Regex.Split(
                    normalized,
                    @"(?<=[.!?;])\s+|\r?\n|(?=\b(?:Then|Next|After that)\b)",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant)
                .SelectMany(
                    clause =>
                        Regex.Split(
                            clause,
                            @"\s+(?:and\s+then|then)\s+",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant))
                .Count(
                    clause =>
                        IsSceneOrComponentMutationSentence(clause) &&
                        Regex.IsMatch(
                            clause,
                            @"\b(?:open|create|construct|build|add|attach|configure|wire|assign|place|save|modify|edit|set)\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant));
        }


        private static bool OwnsSingleTransactionalSceneResource(
            JObject child)
        {
            string[] resourceKeys =
                (child?["MutatedResourceKeys"] as JArray ?? new JArray())
                    .Values<string>()
                    .Where(key => !string.IsNullOrWhiteSpace(key))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            string[] producedScenes =
                (child?["ProducedArtifactPaths"] as JArray ?? new JArray())
                    .Values<string>()
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(path) &&
                            path.EndsWith(
                                ".unity",
                                StringComparison.OrdinalIgnoreCase))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (resourceKeys.Length != 1 ||
                !resourceKeys[0].StartsWith(
                    "scene:",
                    StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            string resourceScenePath =
                resourceKeys[0]
                    .Substring("scene:".Length)
                    .Replace('\\', '/');

            // A scene creator owns the persisted scene it produces. A later
            // scene mutation/verification card consumes that existing scene,
            // so it may legitimately have no produced .unity path.
            return producedScenes.Length == 0 ||
                   (producedScenes.Length == 1 &&
                    string.Equals(
                        producedScenes[0].Replace('\\', '/'),
                        resourceScenePath,
                        StringComparison.OrdinalIgnoreCase));
        }


        private static bool IsStructuredScriptOnlyChild(
            JObject child)
        {
            string[] producedPaths =
                (child?["ProducedArtifactPaths"] as JArray ?? new JArray())
                    .Values<string>()
                    .Where(path => !string.IsNullOrWhiteSpace(path))
                    .ToArray();

            if (producedPaths.Length == 0 ||
                !producedPaths.All(
                    path =>
                        path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ||
                        path.EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase)))
            {
                return false;
            }

            bool mutatesSerializedResource =
                ((child?["MutatedResourceKeys"] as JArray)?.Count ?? 0) > 0;

            bool consumesScene =
                (child?["ConsumedArtifactPaths"] as JArray ?? new JArray())
                    .Values<string>()
                    .Any(
                        path =>
                            !string.IsNullOrWhiteSpace(path) &&
                            path.EndsWith(
                                ".unity",
                                StringComparison.OrdinalIgnoreCase));

            return !mutatesSerializedResource &&
                   !consumesScene;
        }


        private static bool HasStructuredOwnershipContract(
            JObject child)
        {
            return child?.Property("ProducedArtifactPaths") != null ||
                   child?.Property("ConsumedArtifactPaths") != null ||
                   child?.Property("MutatedResourceKeys") != null;
        }


        private static void ValidateStructuredArtifactGraph(
            IReadOnlyList<JObject> children,
            IReadOnlyDictionary<string, JObject> byId)
        {
            bool hasStructuredOwnership =
                children.Any(
                    child =>
                        ((child["ProducedArtifactPaths"] as JArray)?.Count ?? 0) > 0 ||
                        ((child["ConsumedArtifactPaths"] as JArray)?.Count ?? 0) > 0 ||
                        ((child["MutatedResourceKeys"] as JArray)?.Count ?? 0) > 0);

            // Preserve compatibility with already-persisted decomposition
            // batches. Every newly prompted batch supplies these fields.
            if (!hasStructuredOwnership)
                return;

            string[] missingOwnershipTitles =
                children
                    .Where(
                        child =>
                            ((child["ProducedArtifactPaths"] as JArray)?.Count ?? 0) == 0 &&
                            ((child["ConsumedArtifactPaths"] as JArray)?.Count ?? 0) == 0 &&
                            ((child["MutatedResourceKeys"] as JArray)?.Count ?? 0) == 0)
                    .Select(
                        child =>
                            child["Title"]?.Value<string>() ?? string.Empty)
                    .ToArray();

            if (missingOwnershipTitles.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition mixes structured ownership with undeclared children: " +
                    string.Join(", ", missingOwnershipTitles.Select(title => $"'{title}'")) +
                    ". Every sibling in the batch must declare its produced paths, consumed paths, or shared mutable resource keys so scheduling and review scopes remain deterministic.");
            }

            foreach (JObject consumer in children)
            {
                foreach (string consumedPath in
                    (consumer["ConsumedArtifactPaths"] as JArray ?? new JArray())
                        .Values<string>()
                        .Where(path => !string.IsNullOrWhiteSpace(path)))
                {
                    JObject canonicalOwner =
                        children
                            .FirstOrDefault(
                                producer =>
                                    (producer["ProducedArtifactPaths"] as JArray ?? new JArray())
                                        .Values<string>()
                                        .Any(
                                            path =>
                                                string.Equals(
                                                    path,
                                                    consumedPath,
                                                    StringComparison.OrdinalIgnoreCase)));

                    if (canonicalOwner == null)
                    {
                        throw new InvalidOperationException(
                            $"Granular decomposition declares consumed artifact '{consumedPath}' in '{consumer["Title"]?.Value<string>()}' but no sibling owns it through ProducesArtifactPaths.");
                    }

                    if (!ReferenceEquals(canonicalOwner, consumer) &&
                        !DependsTransitively(
                            consumer,
                            canonicalOwner,
                            byId))
                    {
                        throw new InvalidOperationException(
                            $"Artifact consumer '{consumer["Title"]?.Value<string>()}' is not dependency-ordered after the canonical owner of '{consumedPath}'. Declare the producer through DependsOnSiblingTitles.");
                    }
                }
            }

            var resourceWriters =
                children
                    .SelectMany(
                        child =>
                            (child["MutatedResourceKeys"] as JArray ?? new JArray())
                                .Values<string>()
                                .Where(key => !string.IsNullOrWhiteSpace(key))
                                .Select(key => new { Key = key, Child = child }))
                    .GroupBy(entry => entry.Key, StringComparer.OrdinalIgnoreCase);

            foreach (var resourceGroup in resourceWriters)
            {
                JObject[] writers =
                    resourceGroup.Select(entry => entry.Child).ToArray();

                for (int left = 0; left < writers.Length; left++)
                {
                    for (int right = left + 1; right < writers.Length; right++)
                    {
                        if (!DependsTransitively(writers[left], writers[right], byId) &&
                            !DependsTransitively(writers[right], writers[left], byId))
                        {
                            throw new InvalidOperationException(
                                $"Shared mutable resource '{resourceGroup.Key}' is used by unordered siblings '{writers[left]["Title"]?.Value<string>()}' and '{writers[right]["Title"]?.Value<string>()}'. Put every mutation and verification of that resource in one dependency chain.");
                        }
                    }
                }
            }
        }


        private static void ValidatePlayableGameTestChain(
            JObject context)
        {
            List<JObject> children =
                (context?["DirectChildren"] as JArray ?? new JArray())
                    .OfType<JObject>()
                    .ToList();

            string[] duplicateTitles =
                children
                    .Select(
                        child =>
                            child["Title"]?.Value<string>()?.Trim() ??
                            string.Empty)
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .GroupBy(
                        title => title,
                        StringComparer.OrdinalIgnoreCase)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key)
                    .ToArray();

            if (duplicateTitles.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains duplicate sibling titles, so title-based legacy dependencies are ambiguous: " +
                    string.Join(", ", duplicateTitles) +
                    ". Remove the malformed duplicate and retry decomposition.");
            }

            string[] unresolvedDependencies =
                children
                    .SelectMany(
                        child =>
                            (child["UnresolvedDependencyTitles"] as JArray ??
                             new JArray()).Values<string>())
                    .Where(title => !string.IsNullOrWhiteSpace(title))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (unresolvedDependencies.Length > 0)
            {
                throw new InvalidOperationException(
                    "Granular decomposition contains unresolved sibling dependencies after the complete batch was created: " +
                    string.Join(", ", unresolvedDependencies) +
                    ". Add the missing prerequisite cards or correct their titles.");
            }

            bool hasDependencyGraph =
                children.Any(
                    child =>
                        child["DependsOnTaskIds"] is JArray dependencies &&
                        dependencies.Count > 0);

            Dictionary<string, JObject> dependencyGraphById = null;

            if (hasDependencyGraph)
            {
                dependencyGraphById =
                    children
                        .Where(
                            child =>
                                !string.IsNullOrWhiteSpace(
                                    child["TaskId"]?.Value<string>()))
                        .ToDictionary(
                            child => child["TaskId"].Value<string>(),
                            StringComparer.Ordinal);

                string cycle =
                    FindDependencyCycle(
                        children,
                        dependencyGraphById);

                if (!string.IsNullOrWhiteSpace(cycle))
                {
                    throw new InvalidOperationException(
                        "Granular decomposition contains a dependency cycle: " +
                        cycle +
                        ". Repair the graph before implementation continues.");
                }
            }

            Func<JObject, string> text =
                child =>
                    string.Join(
                        "\n",
                        child["Title"]?.Value<string>() ?? string.Empty,
                        child["Description"]?.Value<string>() ?? string.Empty,
                        child["AcceptanceCriteria"]?.Value<string>() ?? string.Empty);

            string[] canonicalScenePaths =
                children
                    .SelectMany(
                        child =>
                            Regex.Matches(
                                    text(child),
                                    @"Assets(?:[\/][A-Za-z0-9_.-]+)+\.unity\b",
                                    RegexOptions.IgnoreCase |
                                    RegexOptions.CultureInvariant)
                                .Cast<Match>()
                                .Select(
                                    match =>
                                        match.Value.Replace('\\', '/')))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (canonicalScenePaths.Length != 1)
            {
                throw new InvalidOperationException(
                    canonicalScenePaths.Length == 0
                        ? "Granular decomposition is incomplete: a playable game requires one exact canonical Assets/.../.unity scene path shared by every scene producer and consumer."
                        : "Granular decomposition contains conflicting canonical scene paths: " +
                          string.Join(", ", canonicalScenePaths) +
                          ". Use exactly one shared scene identity.");
            }

            Func<JObject, bool> isRuntimeAsmdef =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"Assets/[A-Za-z0-9_./-]*\.Runtime\.asmdef|\bruntime\s+assembly[ -]?definition\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant) &&
                        !Regex.IsMatch(
                            text(child),
                            @"\.PlayModeTests\.asmdef|\b(?:Play\s*Mode\s+)?test\s+assembly[ -]?definition\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            Func<JObject, bool> isRuntimeScript =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"\b(?:write|author|create|implement)\b[\s\S]{0,120}Assets/[A-Za-z0-9_./-]+\.cs\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant) &&
                        !Regex.IsMatch(
                            text(child),
                            @"\.PlayModeTests\.asmdef|\bPlay\s*Mode\b|\bsmoke\s+test\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            Func<JObject, bool> isTestAsmdef =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"Assets/[A-Za-z0-9_./-]*\.PlayModeTests\.asmdef|\bPlay\s*Mode\b[\s\S]{0,80}\btest\s+assembly[ -]?definition\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            Func<JObject, bool> isAuthorTest =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"\b(?:author|write|create)\b[\s\S]{0,100}\bPlay\s*Mode\b[\s\S]{0,100}\b(?:smoke\s+test|test)\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant) &&
                        Regex.IsMatch(
                            text(child),
                            @"\.cs\b|test\s+source",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            Func<JObject, bool> isCompile =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"\bcompil(?:e|ation)\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            Func<JObject, bool> isRunTest =
                child =>
                        text(child).IndexOf(
                            "run_play_mode_tests",
                            StringComparison.OrdinalIgnoreCase) >= 0;

            Func<JObject, bool> isFinalVerification =
                child =>
                        Regex.IsMatch(
                            text(child),
                            @"\bfinal\b[\s\S]{0,80}\b(?:integrated|integration|verification)\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant);

            bool hasTestabilitySeam =
                children.Any(
                    child =>
                        Regex.IsMatch(
                            text(child),
                            @"\b(?:test\s+seam|injected\s+input|input\s+source|public\s+(?:command|input|control)\s+method)\b",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant));

            bool valid;

            if (hasDependencyGraph)
            {
                Dictionary<string, JObject> byId =
                    dependencyGraphById;

                JObject runtimeAsmdef =
                    children.FirstOrDefault(isRuntimeAsmdef);
                JObject testAsmdef =
                    children.FirstOrDefault(
                        child =>
                            isTestAsmdef(child) &&
                            DependsTransitively(child, runtimeAsmdef, byId));
                JObject authorTest =
                    children.FirstOrDefault(
                        child =>
                            isAuthorTest(child) &&
                            DependsTransitively(child, testAsmdef, byId));
                JObject postTestCompile =
                    children.FirstOrDefault(
                        child =>
                            isCompile(child) &&
                            DependsTransitively(child, authorTest, byId));
                JObject runTest =
                    children.FirstOrDefault(
                        child =>
                            isRunTest(child) &&
                            DependsTransitively(child, postTestCompile, byId));
                JObject finalVerification =
                    children.FirstOrDefault(
                        child =>
                            isFinalVerification(child) &&
                            DependsTransitively(child, runTest, byId));

                List<JObject> runtimeScripts =
                    children.Where(isRuntimeScript).ToList();

                valid =
                    runtimeAsmdef != null &&
                    runtimeScripts.Count > 0 &&
                    runtimeScripts.All(
                        script =>
                            DependsTransitively(
                                script,
                                runtimeAsmdef,
                                byId)) &&
                    testAsmdef != null &&
                    authorTest != null &&
                    postTestCompile != null &&
                    runTest != null &&
                    finalVerification != null &&
                    hasTestabilitySeam;
            }
            else
            {
                int runtimeAsmdef = children.FindIndex(child => isRuntimeAsmdef(child));
                int firstRuntimeScript = children.FindIndex(child => isRuntimeScript(child));
                int testAsmdef = children.FindIndex(child => isTestAsmdef(child));
                int authorTest = children.FindIndex(child => isAuthorTest(child));
                int postTestCompile =
                    children.FindIndex(
                        Math.Max(authorTest + 1, 0),
                        child => isCompile(child));
                int runTest = children.FindIndex(child => isRunTest(child));
                int finalVerification = children.FindIndex(child => isFinalVerification(child));

                valid =
                    runtimeAsmdef >= 0 &&
                    (firstRuntimeScript < 0 || runtimeAsmdef < firstRuntimeScript) &&
                    testAsmdef > runtimeAsmdef &&
                    authorTest > testAsmdef &&
                    postTestCompile > authorTest &&
                    runTest > postTestCompile &&
                    finalVerification > runTest &&
                    hasTestabilitySeam;
            }

            if (!valid)
            {
                throw new InvalidOperationException(
                    "Granular decomposition is incomplete: a playable game requires an ordered runtime asmdef -> runtime scripts/scene -> PlayMode test asmdef -> PlayMode test source -> post-test compilation -> run_play_mode_tests -> final verification chain. The movement/input contract must also expose a deterministic test seam; unavailable synthetic InputHelpers must not be used.");
            }
        }


        private static bool DependsTransitively(
            JObject task,
            JObject prerequisite,
            IReadOnlyDictionary<string, JObject> byId)
        {
            if (task == null || prerequisite == null)
                return false;

            string prerequisiteId =
                prerequisite["TaskId"]?.Value<string>();

            if (string.IsNullOrWhiteSpace(prerequisiteId))
                return false;

            var pending = new Stack<JObject>();
            var visited = new HashSet<string>(StringComparer.Ordinal);
            pending.Push(task);

            while (pending.Count > 0)
            {
                JObject current = pending.Pop();

                foreach (string dependencyId in
                         (current["DependsOnTaskIds"] as JArray ?? new JArray())
                            .Values<string>())
                {
                    if (string.Equals(
                            dependencyId,
                            prerequisiteId,
                            StringComparison.Ordinal))
                    {
                        return true;
                    }

                    if (visited.Add(dependencyId) &&
                        byId.TryGetValue(dependencyId, out JObject dependency))
                    {
                        pending.Push(dependency);
                    }
                }
            }

            return false;
        }


        private static string FindDependencyCycle(
            IEnumerable<JObject> children,
            IReadOnlyDictionary<string, JObject> byId)
        {
            var visited = new HashSet<string>(StringComparer.Ordinal);
            var visiting = new HashSet<string>(StringComparer.Ordinal);
            var path = new List<string>();

            foreach (JObject child in children)
            {
                string cycle =
                    FindDependencyCycle(
                        child,
                        byId,
                        visited,
                        visiting,
                        path);

                if (!string.IsNullOrWhiteSpace(cycle))
                    return cycle;
            }

            return null;
        }


        private static string FindDependencyCycle(
            JObject child,
            IReadOnlyDictionary<string, JObject> byId,
            ISet<string> visited,
            ISet<string> visiting,
            IList<string> path)
        {
            string id = child?["TaskId"]?.Value<string>();

            if (string.IsNullOrWhiteSpace(id) || visited.Contains(id))
                return null;

            if (!visiting.Add(id))
            {
                int cycleStart = path.IndexOf(id);
                IEnumerable<string> cycleIds =
                    cycleStart >= 0
                        ? path.Skip(cycleStart)
                        : path;

                return string.Join(
                    " -> ",
                    cycleIds
                        .Concat(new[] { id })
                        .Select(
                            cycleId =>
                                byId.TryGetValue(cycleId, out JObject item)
                                    ? item["Title"]?.Value<string>() ?? cycleId
                                    : cycleId));
            }

            path.Add(id);

            foreach (string dependencyId in
                     (child["DependsOnTaskIds"] as JArray ?? new JArray())
                        .Values<string>())
            {
                if (byId.TryGetValue(dependencyId, out JObject dependency))
                {
                    string cycle =
                        FindDependencyCycle(
                            dependency,
                            byId,
                            visited,
                            visiting,
                            path);

                    if (!string.IsNullOrWhiteSpace(cycle))
                        return cycle;
                }
            }

            path.RemoveAt(path.Count - 1);
            visiting.Remove(id);
            visited.Add(id);
            return null;
        }


        private static bool RequiresPlayableGameSmokeTest(
            string request)
        {
            return Regex.IsMatch(
                request ?? string.Empty,
                @"\b(?:Play\s*Mode\s+(?:smoke\s+)?test|smoke\s+test|run_play_mode_tests)\b",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static bool RequiresPlayModeTestAuthoring(
            string request)
        {
            return Regex.IsMatch(
                request ?? string.Empty,
                @"\b(?:author|write|create|implement)\b[\s\S]{0,160}\bPlay\s*Mode\b[\s\S]{0,120}\b(?:test|smoke\s+test)\b",
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static void VerifyExplicitAssetDeliverables(
            string request,
            string workspaceRoot)
        {
            VerifyAssetDeliverables(
                request,
                workspaceRoot,
                null);
        }


        private static void VerifyAssetDeliverables(
            string request,
            string workspaceRoot,
            IReadOnlyCollection<string> structuredProducedArtifactPaths)
        {
            if (string.IsNullOrWhiteSpace(workspaceRoot))
            {
                return;
            }

            IReadOnlyList<string> paths =
                structuredProducedArtifactPaths != null
                    ? structuredProducedArtifactPaths
                        .Where(path => !string.IsNullOrWhiteSpace(path))
                        .Select(
                            path =>
                                path.Trim()
                                    .Replace('\\', '/'))
                        .Distinct(StringComparer.OrdinalIgnoreCase)
                        .ToList()
                    : RequestsAssetCreation(request)
                        ? ExtractExplicitAssetDeliverablePaths(
                            request)
                        : Array.Empty<string>();

            if (paths.Count == 0)
                return;

            string root =
                Path.GetFullPath(workspaceRoot)
                    .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) +
                Path.DirectorySeparatorChar;

            List<string> missing =
                new List<string>();

            foreach (string relativePath in paths)
            {
                string candidate =
                    Path.GetFullPath(
                        Path.Combine(
                            root,
                            relativePath.Replace('/', Path.DirectorySeparatorChar)));

                if (!candidate.StartsWith(root, StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException(
                        $"Explicit asset deliverable escapes the configured workspace: {relativePath}");
                }

                if (!File.Exists(candidate) &&
                    !Directory.Exists(candidate))
                {
                    missing.Add(relativePath);
                }
            }

            if (missing.Count > 0)
            {
                throw new InvalidOperationException(
                    "The agent claimed completion, but these explicit asset deliverables do not exist: " +
                    string.Join(", ", missing));
            }
        }


        private static IReadOnlyCollection<string> ExtractStructuredProducedArtifactPaths(
            string kanbanTaskContextJson)
        {
            if (string.IsNullOrWhiteSpace(kanbanTaskContextJson))
                return null;

            try
            {
                JObject context =
                    JObject.Parse(kanbanTaskContextJson);

                JProperty property =
                    context.Property(
                        "ProducedArtifactPaths",
                        StringComparison.OrdinalIgnoreCase);

                if (property == null)
                    return null;

                return (property.Value as JArray ?? new JArray())
                    .Values<string>()
                    .Where(path => !string.IsNullOrWhiteSpace(path))
                    .Select(
                        path =>
                            path.Trim()
                                .Replace('\\', '/'))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }
            catch (JsonException)
            {
                // Only a genuinely unavailable scoped contract may fall back
                // to legacy prose extraction. An authoritative empty manifest
                // must remain empty rather than acquiring inferred outputs.
                return null;
            }
        }


        private static IReadOnlyCollection<string> ExtractStructuredMutatedResourceKeys(
            string kanbanTaskContextJson)
        {
            if (string.IsNullOrWhiteSpace(kanbanTaskContextJson))
                return null;

            try
            {
                JObject context = JObject.Parse(kanbanTaskContextJson);
                JProperty property = context.Property(
                    "MutatedResourceKeys",
                    StringComparison.OrdinalIgnoreCase);

                if (property == null)
                    return null;

                return (property.Value as JArray ?? new JArray())
                    .Values<string>()
                    .Where(key => !string.IsNullOrWhiteSpace(key))
                    .Select(key => key.Trim().Replace('\\', '/'))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }
            catch (JsonException)
            {
                return null;
            }
        }


        private static bool IsStructuredChildTask(
            string kanbanTaskContextJson)
        {
            if (string.IsNullOrWhiteSpace(kanbanTaskContextJson))
            {
                return false;
            }

            try
            {
                return JObject.Parse(kanbanTaskContextJson)
                    .Value<bool?>("IsChildTask") == true;
            }
            catch (JsonException)
            {
                return false;
            }
        }


        private static void VerifyPersistedSceneObjectCompletion(
            string gameObjectName,
            ToolResultCaptureObserver sceneSaveCapture,
            ToolResultCaptureObserver sceneInspectionCapture)
        {
            if (sceneSaveCapture ==
                    null ||
                sceneSaveCapture.CompletedCount ==
                    0)
            {
                throw new InvalidOperationException(
                    $"The agent claimed completion for GameObject '{gameObjectName}', but save_editor_scene did not complete successfully. A transient object in an untitled or unsaved scene is not a completed deliverable.");
            }


            JObject saveResult =
                JObject.Parse(
                    sceneSaveCapture.Result ??
                    "{}");

            string scenePath =
                saveResult["ScenePath"]
                    ?.Value<string>();

            if (saveResult["Succeeded"]?.Value<bool>() !=
                    true ||
                saveResult["Saved"]?.Value<bool>() !=
                    true ||
                saveResult["DirtyAfterSave"]?.Value<bool>() !=
                    false ||
                string.IsNullOrWhiteSpace(
                    scenePath) ||
                !scenePath.StartsWith(
                    "Assets/",
                    StringComparison.OrdinalIgnoreCase) ||
                !scenePath.EndsWith(
                    ".unity",
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException(
                    $"The agent claimed completion for GameObject '{gameObjectName}', but save_editor_scene did not prove a clean persisted Assets/.../.unity scene.");
            }


            if (SaveResultVerifiesPersistedGameObject(
                    saveResult,
                    gameObjectName))
            {
                // save_editor_scene captures DirtyAfterSave and the persisted
                // hierarchy atomically. This later mutation evidence remains
                // authoritative even when an earlier inspect_scene call was
                // captured before the final save and therefore became stale.
                return;
            }


            if (sceneInspectionCapture == null ||
                sceneInspectionCapture.CompletedCount == 0)
            {

                throw new InvalidOperationException(
                    $"The agent claimed completion for GameObject '{gameObjectName}', but neither inspect_scene nor save_editor_scene verified that object in the persisted scene.");
            }


            JObject inspectionResult =
                JObject.Parse(
                    sceneInspectionCapture.Result ??
                    "{}");

            string inspectedPath =
                inspectionResult["Path"]
                    ?.Value<string>();

            if (inspectionResult["Exists"]?.Value<bool>() !=
                    true ||
                inspectionResult["Dirty"]?.Value<bool>() !=
                    false ||
                !string.Equals(
                    inspectedPath,
                    scenePath,
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException(
                    $"The agent claimed completion for GameObject '{gameObjectName}', but the final inspect_scene result did not verify the saved scene '{scenePath}' with Dirty=false.");
            }
        }


        private static bool SaveResultVerifiesPersistedGameObject(
            JObject saveResult,
            string gameObjectName)
        {
            if (saveResult == null ||
                string.IsNullOrWhiteSpace(gameObjectName) ||
                saveResult["Succeeded"]?.Value<bool>() != true ||
                saveResult["Saved"]?.Value<bool>() != true ||
                saveResult["DirtyAfterSave"]?.Value<bool>() != false ||
                saveResult["PersistenceVerified"]?.Value<bool>() != true)
            {
                return false;
            }

            JArray persistedPaths =
                saveResult["PersistedGameObjectPaths"] as JArray ??
                new JArray();

            return persistedPaths
                .Values<string>()
                .Any(
                    path =>
                        string.Equals(
                            path,
                            gameObjectName,
                            StringComparison.OrdinalIgnoreCase) ||
                        path.EndsWith(
                            "/" + gameObjectName,
                            StringComparison.OrdinalIgnoreCase));
        }


        private static bool RequestsAssetCreation(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return false;

            return request.IndexOf("create", StringComparison.OrdinalIgnoreCase) >= 0 ||
                   request.IndexOf("write", StringComparison.OrdinalIgnoreCase) >= 0 ||
                   request.IndexOf("author", StringComparison.OrdinalIgnoreCase) >= 0 ||
                   request.IndexOf("implement", StringComparison.OrdinalIgnoreCase) >= 0 ||
                   request.IndexOf("build", StringComparison.OrdinalIgnoreCase) >= 0;
        }


        private static IReadOnlyList<string> ExtractExplicitAssetPaths(
            string request)
        {
            return Regex.Matches(
                    request ?? string.Empty,
                    @"Assets(?:[\\/][A-Za-z0-9_.-]+)+",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant)
                .Cast<Match>()
                .Select(match =>
                    match.Value
                        .Replace('\\', '/')
                        .TrimEnd('.', ',', ';', ':'))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToList();
        }


        private static IReadOnlyList<string> ExtractExplicitAssetDeliverablePaths(
            string request)
        {
            string normalized =
                request ??
                string.Empty;

            List<string> deliverables =
                new List<string>();

            foreach (
                Match match
                in Regex.Matches(
                    normalized,
                    @"Assets(?:[\/][A-Za-z0-9_.-]+)+",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant)
                    .Cast<Match>())
            {
                string path =
                    match.Value
                        .Replace('\\', '/')
                        .TrimEnd('.', ',', ';', ':');

                int effectivePathLength =
                    path.Length;

                int clauseStart =
                    FindPreviousClauseBoundary(
                        normalized,
                        match.Index);

                int clauseEnd =
                    FindNextClauseBoundary(
                        normalized,
                        match.Index + effectivePathLength);

                string clause =
                    normalized.Substring(
                        clauseStart,
                        clauseEnd - clauseStart);

                bool hasLocalMutation =
                    Regex.IsMatch(
                        clause,
                        @"\b(?:create|write|author|implement|generate|build|save|copy|move|export)\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                int pathOffset =
                    match.Index - clauseStart;

                string afterPath =
                    clause.Substring(
                        Math.Min(
                            clause.Length,
                            pathOffset + effectivePathLength));

                bool explicitlyRequiredToExist =
                    Regex.IsMatch(
                        afterPath,
                        @"^\s*(?:must\s+)?(?:exists?|is\s+(?:created|saved|present)|was\s+(?:created|saved))\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                string beforePath =
                    clause.Substring(
                        0,
                        Math.Max(0, pathOffset));

                bool contextOnlyReference =
                    Regex.IsMatch(
                        beforePath,
                        @"\b(?:consumed\s+by|references?|targets?|loads?|reads?|uses?|against|from)\b[^.!?;\r\n]{0,120}$",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                bool negatedReference =
                    Regex.IsMatch(
                        beforePath,
                        @"\b(?:no|not|never|without|excluding|exclude|forbid(?:den)?)\b[^.!?;\r\n]{0,100}$",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);

                if (negatedReference ||
                    ((!hasLocalMutation || contextOnlyReference) &&
                     !explicitlyRequiredToExist))
                {
                    continue;
                }

                if (!deliverables.Contains(
                        path,
                        StringComparer.OrdinalIgnoreCase))
                {
                    deliverables.Add(path);
                }
            }

            return deliverables;
        }


        private static int FindPreviousClauseBoundary(
            string value,
            int beforeIndex)
        {
            for (int index = beforeIndex - 1; index >= 0; index--)
            {
                if (value[index] == '.' ||
                    value[index] == '!' ||
                    value[index] == '?' ||
                    value[index] == ';' ||
                    value[index] == '\n' ||
                    value[index] == '\r')
                {
                    return index + 1;
                }
            }

            return 0;
        }


        private static int FindNextClauseBoundary(
            string value,
            int fromIndex)
        {
            for (int index = fromIndex; index < value.Length; index++)
            {
                if (value[index] == '.' ||
                    value[index] == '!' ||
                    value[index] == '?' ||
                    value[index] == ';' ||
                    value[index] == '\n' ||
                    value[index] == '\r')
                {
                    return index;
                }
            }

            return value.Length;
        }


        private static string ExtractExpectedCreatedGameObjectName(
            string request)
        {
            string normalized =
                RemoveSiblingDependencyClauses(
                    request);

            Match match =
                Regex.Match(
                    normalized,
                    @"\b(?:create|author)\s+(?:(?:a|an|the)\s+)?(?<name>[A-Za-z_][A-Za-z0-9_.-]*(?:\s+[A-Za-z_][A-Za-z0-9_.-]*){0,3}?)\s+GameObject\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (!match.Success)
            {
                match =
                    Regex.Match(
                        normalized,
                        @"\b(?:GameObject|Cube|Sphere|Capsule|Cylinder|Plane|Quad)\s+(?:named|called)\s+[`'""]?(?<name>[A-Za-z_][A-Za-z0-9_.-]*)",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);
            }

            if (!match.Success)
                return null;

            string name =
                NormalizeGameObjectNameCandidate(
                    match.Groups["name"].Value);

            // A compact identifier in the card is an explicit object name.
            // Preserve it exactly for authoritative verification.
            if (Regex.IsMatch(
                    name,
                    @"^[A-Za-z_][A-Za-z0-9_.-]*$",
                    RegexOptions.CultureInvariant))
            {
                return name;
            }

            if (name.IndexOf("Main Camera", StringComparison.OrdinalIgnoreCase) >= 0 ||
                name.IndexOf("camera", StringComparison.OrdinalIgnoreCase) >= 0)
                return "Main Camera";

            if (name.IndexOf("Directional", StringComparison.OrdinalIgnoreCase) >= 0 &&
                name.IndexOf("Light", StringComparison.OrdinalIgnoreCase) >= 0)
                return "Directional Light";

            // A plural/count phrase describes a collection, not one exact
            // persisted GameObject identity.
            if (Regex.IsMatch(
                    name,
                    @"^(?:(?:two|three|four|five|six|seven|eight|nine|ten)|\d+)\b.*s$",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant))
            {
                return null;
            }

            return ToPascalCaseIdentifier(name);
        }


        private static string ToPascalCaseIdentifier(
            string value)
        {
            MatchCollection words =
                Regex.Matches(
                    value ?? string.Empty,
                    @"[A-Za-z0-9]+",
                    RegexOptions.CultureInvariant);

            return string.Concat(
                words.Cast<Match>()
                    .Select(
                        word =>
                            char.ToUpperInvariant(word.Value[0]) +
                            word.Value.Substring(1)));
        }


        private static string ExtractExplicitScenePath(
            string request)
        {
            Match match =
                Regex.Match(
                    request ?? string.Empty,
                    @"Assets(?:[\\/][A-Za-z0-9_.-]+)+\.unity\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            return match.Success
                ? match.Value.Replace('\\', '/')
                : null;
        }


        private static string ExtractNamedSceneName(
            string request)
        {
            Match match =
                Regex.Match(
                    request ?? string.Empty,
                    @"\b(?:in|from|within|inspect)\s+(?:the\s+)?(?<name>[A-Za-z_][A-Za-z0-9_.-]*)\s+scene\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            return match.Success
                ? match.Groups["name"].Value
                : null;
        }


        private static string ResolveNamedSceneAssetPath(
            string request)
        {
            string sceneName =
                ExtractNamedSceneName(
                    request);

            return string.IsNullOrWhiteSpace(
                    sceneName)
                ? null
                : EditorSceneAuthoringUtility
                    .ResolveUniqueExistingSceneAssetPath(
                        sceneName + ".unity");
        }


        private static string NormalizeExistingSceneReferences(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return request;
            }

            return Regex.Replace(
                request,
                @"Assets(?:[\\/][A-Za-z0-9_.-]+)+\.unity\b",
                match =>
                    EditorSceneAuthoringUtility
                        .ResolveUniqueExistingSceneAssetPath(
                            match.Value.Replace('\\', '/')) ??
                    match.Value,
                RegexOptions.IgnoreCase |
                RegexOptions.CultureInvariant);
        }


        private static bool ShouldAvoidCapturedEditorContinuation(
            bool reviewMode,
            string expectedCreatedGameObject)
        {
            // Retained as a testable policy boundary. Editor-capability
            // recovery may occur after the initial model run for every card,
            // so no runner path may intentionally abandon the captured Unity
            // synchronization context.
            return false;
        }


        private static bool RequiresComponentInspection(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            string normalized =
                RemoveSiblingDependencyClauses(
                    request)
                    .Trim();

            bool verifiesInspectionCapability =
                string.Equals(
                    ExtractVerifiedCapabilityName(
                        normalized),
                    "inspect_editor_component",
                    StringComparison.Ordinal);

            // A card which creates, edits, or verifies one explicit .cs file
            // is source/compilation work unless it also names a live scene,
            // prefab, selection, or component-inspection capability. Terms
            // such as Rigidbody and component may legitimately appear in the
            // source contract and must not manufacture a selected-object
            // inspection prerequisite.
            if (!verifiesInspectionCapability &&
                IsStandaloneScriptAssetTask(
                    normalized))
            {
                return false;
            }

            bool isCompilationPersistenceGate =
                RequiresReviewCompilationEvidence(
                    normalized) &&
                Regex.IsMatch(
                    normalized,
                    @"\b(?:save|saved|persistence|dirty|scene)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool explicitlyRequestsComponentInspection =
                Regex.IsMatch(
                    normalized,
                    @"\b(?:inspect|troubleshoot|diagnos(?:e|is|tic)?|verify|confirm)\b[\s\S]{0,120}\b(?:component|Collider|Rigidbody|Renderer|MeshFilter|Transform)\b|\b(?:component|Collider|Rigidbody|Renderer|MeshFilter|Transform)\b[\s\S]{0,120}\b(?:inspect|troubleshoot|diagnos(?:e|is|tic)?|verify|confirm)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            // A save/compile gate verifies persistence and compiler state.
            // Conditional wording such as "fix any compile error" must not
            // turn it into component troubleshooting merely because script
            // names or scene objects appear elsewhere in the card.
            if (isCompilationPersistenceGate &&
                !verifiesInspectionCapability &&
                !explicitlyRequestsComponentInspection)
            {
                return false;
            }

            // Scene-building cards inspect objects they create or resolve by
            // name. Merely naming an inspection capability as a later
            // verification step must not impose a live-selection prerequisite.
            if (!verifiesInspectionCapability &&
                RequiresSceneAuthoring(
                    normalized))
            {
                return false;
            }

            bool mentionsComponent =
                normalized.IndexOf(
                    "component",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "collider",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "rigidbody",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "renderer",
                    StringComparison.OrdinalIgnoreCase) >=
                0;

            string namedTarget =
                ExtractReviewGameObjectName(
                    normalized);

            bool namesTargetComponent =
                !string.IsNullOrWhiteSpace(
                    namedTarget) &&
                !string.IsNullOrWhiteSpace(
                    ExtractTargetComponentType(
                        namedTarget,
                        normalized));

            mentionsComponent =
                mentionsComponent ||
                namesTargetComponent;

            bool requestsInspection =
                normalized.IndexOf(
                    "inspect",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "troubleshoot",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "diagnos",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "not working",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "doesn't work",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "does not work",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "fix",
                    StringComparison.OrdinalIgnoreCase) >=
                    0;

            bool explicitlyNamesComponentInspection =
                Regex.IsMatch(
                    normalized,
                    @"\bcomponent\s+inspection\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool refersToSelectedTarget =
                normalized.IndexOf(
                    "selected",
                    StringComparison.OrdinalIgnoreCase) >=
                0 ||
                normalized.IndexOf(
                    "current selection",
                    StringComparison.OrdinalIgnoreCase) >=
                0;

            bool refersToNamedTarget =
                !string.IsNullOrWhiteSpace(
                    namedTarget) &&
                (normalized.IndexOf(
                     "confirm",
                     StringComparison.OrdinalIgnoreCase) >=
                 0 ||
                 normalized.IndexOf(
                     "verify",
                     StringComparison.OrdinalIgnoreCase) >=
                 0);

            return verifiesInspectionCapability ||
                   (explicitlyNamesComponentInspection &&
                    requestsInspection) ||
                   (mentionsComponent &&
                    requestsInspection &&
                    (refersToSelectedTarget ||
                     refersToNamedTarget));
        }


        private static bool RequiresReviewComponentInspection(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            bool namesInspectableUnityState =
                !string.IsNullOrWhiteSpace(
                    ExtractReviewGameObjectName(
                        request)) &&
                Regex.IsMatch(
                    request,
                    @"\b(?:Collider|Rigidbody|Renderer|MeshFilter|MeshRenderer|Transform|Camera|Light|Material|IsTrigger|constraints?|FreezePosition|FreezeRotation|script)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            return RequiresComponentInspection(
                       request) ||
                   namesInspectableUnityState ||
                   request.IndexOf(
                       "inspect_editor_component",
                       StringComparison.OrdinalIgnoreCase) >=
                       0 ||
                   (request.IndexOf(
                        "component",
                        StringComparison.OrdinalIgnoreCase) >=
                        0 &&
                    (request.IndexOf(
                         "attached",
                         StringComparison.OrdinalIgnoreCase) >=
                         0 ||
                     request.IndexOf(
                         "enabled",
                         StringComparison.OrdinalIgnoreCase) >=
                         0 ||
                     request.IndexOf(
                         "configured",
                         StringComparison.OrdinalIgnoreCase) >=
                         0));
        }


        // Read-only preflight must not silently occupy a review lane indefinitely.
        // Unity API work still runs on the Editor thread; never move it to Task.Run.
        private static async Task<string> CaptureBoundedReviewEvidenceAsync(
            Func<CancellationToken, Task<string>> capture,
            CancellationToken cancellationToken,
            TimeSpan timeout)
        {
            using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            Task<string> operation = capture(deadline.Token);
            Task timer = Task.Delay(timeout, deadline.Token);
            if (await Task.WhenAny(operation, timer) == operation)
            {
                deadline.Cancel();
                return await operation;
            }

            deadline.Cancel();
            // Observe a late fault without keeping the review blocked on a
            // capability that does not cooperate with cancellation.
            _ = operation.ContinueWith(t => { var ignored = t.Exception; },
                CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
            cancellationToken.ThrowIfCancellationRequested();
            throw new InvalidOperationException(
                "[AIRUNTIME_REVIEW_EVIDENCE_INCOMPLETE] Scene evidence preparation timed out after " +
                timeout.TotalSeconds + " seconds. The read was cancelled. Missing evidence must not be treated as an implementation defect or a passing review.");
        }

        private static async Task<string> CaptureSceneRootComponentEvidenceAsync(
            RuntimeFacade runtime,
            string scenePath,
            string request,
            CancellationToken cancellationToken,
            Func<string, bool> canReadScript = null,
            Action<string> reportProgress = null)
        {
            reportProgress?.Invoke("Review preparation: inspecting scene " + scenePath);
            string sceneEvidence =
                await runtime.ExecuteCapabilityAsync(
                    "inspect_scene",
                    JsonConvert.SerializeObject(
                        new
                        {
                            scenePath,
                            includeRootObjects = true
                        }),
                    cancellationToken);

            reportProgress?.Invoke("Review preparation: reading scene hierarchy.");
            string hierarchyEvidence =
                await runtime.ExecuteCapabilityAsync(
                    "get_hierarchy_tree",
                    JsonConvert.SerializeObject(
                        new
                        {
                            scenePath,
                            includeInactive = true,
                            includeComponents = false,
                            maxDepth = 32,
                            maxObjects = 500
                        }),
                    cancellationToken);

            JObject hierarchy =
                JObject.Parse(
                    hierarchyEvidence ??
                    "{}");

            bool inspectAll =
                Regex.IsMatch(
                    request ?? string.Empty,
                    @"\b(?:final|integrated|integration|entire|all\s+(?:required\s+)?GameObjects|smoke\s+test)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            bool incompleteEvidence;

            List<JObject> targets =
                SelectContractHierarchyTargets(
                    hierarchy,
                    request,
                    inspectAll,
                    24,
                    out incompleteEvidence);

            StringBuilder evidence =
                new StringBuilder();

            evidence.AppendLine(
                "AIRuntime authoritative inspect_scene result:");
            evidence.AppendLine(sceneEvidence);

            evidence.AppendLine();
            evidence.AppendLine(
                "AIRuntime authoritative get_hierarchy_tree result:");
            evidence.AppendLine(hierarchyEvidence);

            if (incompleteEvidence)
            {
                evidence.AppendLine();
                evidence.AppendLine(
                    "[AIRUNTIME_REVIEW_EVIDENCE_INCOMPLETE] The bounded hierarchy could not uniquely and completely resolve every contract-driven inspection target. Treat missing proof as an evidence gap, never as an implementation defect.");
            }

            HashSet<string> scriptPaths =
                new HashSet<string>(
                    StringComparer.OrdinalIgnoreCase);

            foreach (JObject target in targets)
            {
                int instanceId =
                    target["InstanceId"].Value<int>();

                reportProgress?.Invoke("Review preparation: inspecting components on " + target["Name"]);
                string componentEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "inspect_editor_component",
                        JsonConvert.SerializeObject(
                            new
                            {
                                gameObjectInstanceId = instanceId,
                                includeSerializedProperties = true
                            }),
                        cancellationToken);

                evidence.AppendLine();
                evidence.AppendLine(
                    $"AIRuntime authoritative inspect_editor_component result for '{target["Name"]?.Value<string>()}':");
                evidence.AppendLine(componentEvidence);

                CollectScriptPaths(
                    componentEvidence,
                    scriptPaths);
            }

            reportProgress?.Invoke("Review preparation: reading permitted component sources.");
            await AppendScriptEvidenceAsync(
                runtime,
                evidence,
                scriptPaths,
                cancellationToken,
                canReadScript);

            return evidence.ToString();
        }


        private static List<JObject> SelectContractHierarchyTargets(
            JObject hierarchy,
            string request,
            bool inspectAll,
            int maxTargets,
            out bool incompleteEvidence)
        {
            List<JObject> nodes =
                new List<JObject>();

            foreach (
                JObject root
                in (hierarchy?["Roots"] as JArray ?? new JArray())
                    .OfType<JObject>())
            {
                AppendHierarchyNodes(
                    root,
                    nodes);
            }

            List<JObject> candidates =
                nodes
                    .Where(
                        node =>
                            node["InstanceId"]?.Value<int>() != 0 &&
                            (inspectAll ||
                             ContractMentionsHierarchyNode(
                                 request,
                                 node)))
                    .GroupBy(
                        node => node["InstanceId"].Value<int>())
                    .Select(group => group.First())
                    .ToList();

            if (candidates.Count == 0 &&
                nodes.Count == 1 &&
                nodes[0]["InstanceId"]?.Value<int>() != 0)
            {
                candidates.Add(nodes[0]);
            }

            bool ambiguousName =
                candidates
                    .Where(
                        node =>
                            !string.IsNullOrWhiteSpace(
                                node["Name"]?.Value<string>()))
                    .GroupBy(
                        node =>
                            node["Name"].Value<string>(),
                        StringComparer.OrdinalIgnoreCase)
                    .Any(
                        group =>
                            group.Count() > 1 &&
                            !group.Any(
                                node =>
                                    ContractMentionsHierarchyPath(
                                        request,
                                        node["HierarchyPath"]?.Value<string>())));

            incompleteEvidence =
                hierarchy?["Truncated"]?.Value<bool>() == true ||
                nodes.Any(
                    node =>
                        node["DepthLimited"]?.Value<bool>() == true) ||
                candidates.Count == 0 ||
                candidates.Count > maxTargets ||
                ambiguousName;

            return candidates
                .Take(
                    Math.Max(
                        0,
                        maxTargets))
                .ToList();
        }


        private static void AppendHierarchyNodes(
            JObject node,
            ICollection<JObject> nodes)
        {
            if (node == null)
                return;

            nodes.Add(node);

            foreach (
                JObject child
                in (node["Children"] as JArray ?? new JArray())
                    .OfType<JObject>())
            {
                AppendHierarchyNodes(
                    child,
                    nodes);
            }
        }


        private static bool ContractMentionsHierarchyNode(
            string request,
            JObject node)
        {
            string name =
                node?["Name"]?.Value<string>();

            if (string.IsNullOrWhiteSpace(name))
                return false;

            if (Regex.IsMatch(
                    request ?? string.Empty,
                    @"(?<![A-Za-z0-9_])" +
                    Regex.Escape(name) +
                    @"(?![A-Za-z0-9_])",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant))
            {
                return true;
            }

            string spacedName =
                Regex.Replace(
                    name,
                    @"(?<=[a-z0-9])(?=[A-Z])",
                    " ");

            return !string.Equals(
                       spacedName,
                       name,
                       StringComparison.Ordinal) &&
                   Regex.IsMatch(
                       request ?? string.Empty,
                       @"(?<![A-Za-z0-9_])" +
                       Regex.Escape(spacedName) +
                       @"(?![A-Za-z0-9_])",
                       RegexOptions.IgnoreCase |
                       RegexOptions.CultureInvariant);
        }


        private static bool ContractMentionsHierarchyPath(
            string request,
            string hierarchyPath)
        {
            if (string.IsNullOrWhiteSpace(hierarchyPath))
                return false;

            string normalizedPath =
                hierarchyPath.Replace('\\', '/');

            return (request ?? string.Empty)
                       .Replace('\\', '/')
                       .IndexOf(
                           normalizedPath,
                           StringComparison.OrdinalIgnoreCase) >= 0;
        }


        private static async Task<string> CaptureSingleComponentReviewEvidenceAsync(
            RuntimeFacade runtime,
            int gameObjectInstanceId,
            bool reviewMode,
            CancellationToken cancellationToken,
            Func<string, bool> canReadScript = null)
        {
            string componentEvidence =
                await runtime.ExecuteCapabilityAsync(
                    "inspect_editor_component",
                    JsonConvert.SerializeObject(
                        new
                        {
                            gameObjectInstanceId,
                            includeSerializedProperties = true
                        }),
                    cancellationToken);

            if (!reviewMode)
                return componentEvidence;

            JObject parsed =
                JObject.Parse(
                    componentEvidence ??
                    "{}");

            StringBuilder evidence =
                new StringBuilder(componentEvidence);

            string inspectedScenePath =
                parsed["ScenePath"]?.Value<string>();

            if (!string.IsNullOrWhiteSpace(inspectedScenePath))
            {
                string sceneEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "inspect_scene",
                        JsonConvert.SerializeObject(
                            new
                            {
                                scenePath = inspectedScenePath,
                                includeRootObjects = true
                            }),
                        cancellationToken);

                evidence.AppendLine();
                evidence.AppendLine();
                evidence.AppendLine(
                    "AIRuntime authoritative inspect_scene result for the inspected GameObject's owning scene:");
                evidence.AppendLine(sceneEvidence);
            }

            HashSet<string> scriptPaths =
                new HashSet<string>(
                    StringComparer.OrdinalIgnoreCase);

            CollectScriptPaths(componentEvidence, scriptPaths);

            await AppendScriptEvidenceAsync(
                runtime,
                evidence,
                scriptPaths,
                cancellationToken,
                canReadScript);

            return evidence.ToString();
        }


        private static async Task<string> CaptureProducedPrefabReviewEvidenceAsync(
            RuntimeFacade runtime,
            IReadOnlyCollection<string> producedArtifactPaths,
            CancellationToken cancellationToken)
        {
            string[] prefabPaths =
                (producedArtifactPaths ?? Array.Empty<string>())
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(path) &&
                            path.EndsWith(
                                ".prefab",
                                StringComparison.OrdinalIgnoreCase))
                    .Select(path => path.Trim().Replace('\\', '/'))
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (prefabPaths.Length == 0)
                return null;

            JArray prefabs =
                new JArray();

            foreach (string prefabPath in prefabPaths)
            {
                cancellationToken.ThrowIfCancellationRequested();

                JObject record =
                    new JObject
                    {
                        ["AssetPath"] = prefabPath
                    };

                try
                {
                    string inspection =
                        await runtime.ExecuteCapabilityAsync(
                            "inspect_prefab",
                            JsonConvert.SerializeObject(
                                new
                                {
                                    assetPath = prefabPath,
                                    includePropertyOverrides = false
                                }),
                            cancellationToken);

                    record["Inspection"] =
                        ToEvidenceToken(inspection);

                    JObject parsedInspection =
                        JObject.Parse(inspection ?? "{}");

                    string globalObjectId =
                        parsedInspection["GlobalObjectId"]
                            ?.Value<string>();

                    if (!string.IsNullOrWhiteSpace(globalObjectId))
                    {
                        string gameObjectInspection =
                            await runtime.ExecuteCapabilityAsync(
                                "inspect_game_object",
                                JsonConvert.SerializeObject(
                                    new
                                    {
                                        globalObjectId,
                                        includeComponents = true
                                    }),
                                cancellationToken);

                        record["GameObjectInspection"] =
                            ToEvidenceToken(gameObjectInspection);

                        int instanceId = record["GameObjectInspection"]?["GameObject"]?["InstanceId"]?.Value<int>() ?? 0;
                        if (instanceId != 0)
                        {
                            record["ComponentInspection"] = ToEvidenceToken(
                                await runtime.ExecuteCapabilityAsync("inspect_editor_component",
                                    JsonConvert.SerializeObject(new { gameObjectInstanceId = instanceId,
                                        includeSerializedProperties = true }), cancellationToken));
                        }
                    }

                    string assetInspection =
                        await runtime.ExecuteCapabilityAsync(
                            "inspect_unity_asset",
                            JsonConvert.SerializeObject(
                                new
                                {
                                    assetPath = prefabPath,
                                    includeDependencies = true,
                                    includeSubAssets = true
                                }),
                            cancellationToken);

                    record["AssetInspection"] =
                        ToEvidenceToken(assetInspection);
                }
                catch (Exception exception) when (
                    !(exception is OperationCanceledException))
                {
                    record["Inspection"] =
                        BuildMissingReviewArtifactEvidence(
                            prefabPath,
                            exception.Message);
                }

                prefabs.Add(record);
            }

            return new JObject
            {
                ["RequestedPrefabCount"] = prefabPaths.Length,
                ["Prefabs"] = prefabs
            }.ToString(Formatting.Indented);
        }


        private static string AppendProducedPrefabReviewEvidence(
            string componentInspectionEvidence,
            string prefabAssetEvidence,
            bool reviewMode)
        {
            if (!reviewMode ||
                string.IsNullOrWhiteSpace(prefabAssetEvidence))
            {
                return componentInspectionEvidence;
            }

            StringBuilder evidence =
                new StringBuilder(
                    componentInspectionEvidence ?? string.Empty);

            if (evidence.Length > 0)
            {
                evidence.AppendLine();
                evidence.AppendLine();
            }

            evidence.AppendLine(
                "AIRuntime authoritative prefab evidence for every declared produced prefab asset. Each record can include inspect_prefab identity, inspect_game_object hierarchy/components/tag/layer/transform, inspect_editor_component current values and decoded Rigidbody constraints, and inspect_unity_asset dependencies/sub-assets. Checkpoint claims are historical and cannot substitute for these current component values:");
            evidence.AppendLine(
                prefabAssetEvidence.Trim());

            evidence.AppendLine();
            evidence.AppendLine(
                "This evidence was captured immediately before review without a provider round trip. Do not call inspect_prefab, inspect_game_object, inspect_unity_asset, file_exists, or get_serialized_properties again for a produced prefab whose record already supplies the required fact. Judge that embedded current state directly; call another read tool only for an exact acceptance criterion that is absent from the record.");

            return evidence.ToString();
        }

        private static string RejectContradictoryPrefabConstraintClaims(string response, string evidence)
        {
            if (string.IsNullOrWhiteSpace(evidence) ||
                !Regex.IsMatch(response ?? string.Empty, @"KANBAN_REVIEW:\s*PASS", RegexOptions.IgnoreCase))
                return response;
            JObject parsed;
            try { parsed = JObject.Parse(evidence); }
            catch (JsonException) { return response; }
            var bodies = parsed.SelectTokens("Prefabs[*].ComponentInspection.Components[*].Rigidbody")
                .OfType<JObject>().ToArray();
            foreach (Match claim in Regex.Matches(response,
                @"\b(?<field>Freeze(?:Position|Rotation)[XYZ])\s*[:=]\s*(?<value>true|false)\b",
                RegexOptions.IgnoreCase))
            {
                string field = claim.Groups["field"].Value;
                bool expected = bool.Parse(claim.Groups["value"].Value);
                var values = bodies.Select(b => b.GetValue(field, StringComparison.OrdinalIgnoreCase))
                    .Where(v => v?.Type == JTokenType.Boolean).Select(v => (bool)v).ToArray();
                // With multiple objects, reject only a claim contradicted by
                // every inspected body. Never borrow another object's identity.
                if (values.Length > 0 && values.All(value => value != expected))
                    return "KANBAN_REVIEW: FAIL\n[ReviewFailureKind: EvidenceGap] The reviewer claimed " +
                        field + "=" + expected.ToString().ToLowerInvariant() +
                        ", but every captured prefab Rigidbody reports " + field + "=" +
                        (!expected).ToString().ToLowerInvariant() +
                        ". Reassess the actual component evidence against the card contract; historical checkpoints cannot override it.";
            }
            return response;
        }


        private static void CollectScriptPaths(
            string componentEvidence,
            ISet<string> paths)
        {
            JObject parsed =
                JObject.Parse(
                    componentEvidence ??
                    "{}");

            foreach (
                string path
                in (parsed["Components"] as JArray ?? new JArray())
                    .OfType<JObject>()
                    .Select(
                        component =>
                            component["ScriptAssetPath"]?.Value<string>())
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(path)))
            {
                paths.Add(path);
            }
        }


        private static async Task AppendScriptEvidenceAsync(
            RuntimeFacade runtime,
            StringBuilder evidence,
            IEnumerable<string> paths,
            CancellationToken cancellationToken,
            Func<string, bool> canReadScript = null)
        {
            foreach (string scriptPath in paths)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (canReadScript != null && !canReadScript(scriptPath))
                {
                    evidence.AppendLine();
                    evidence.AppendLine($"[ComponentSourceOutsideReadScope] Source '{scriptPath}' was not read because it is outside this card's declared readable artifacts. " +
                        "The captured component inspection remains available; no source-content verification is claimed for this component. " +
                        "If the acceptance criteria require this source, report the missing input as an evidence gap rather than requesting the forbidden read.");
                    continue;
                }
                string scriptEvidence =
                    await runtime.ExecuteCapabilityAsync(
                        "read_file",
                        JsonConvert.SerializeObject(
                            new { path = scriptPath }),
                        cancellationToken);

                evidence.AppendLine();
                evidence.AppendLine();
                evidence.AppendLine(
                    $"AIRuntime authoritative read_file result for inspected component script '{scriptPath}':");
                evidence.AppendLine(scriptEvidence);
            }
        }


        private static async Task<int> ResolveTaskGameObjectIdAsync(
            RuntimeFacade runtime,
            string request,
            string liveSelectionEvidence,
            CancellationToken cancellationToken)
        {
            string targetName =
                ExtractReviewGameObjectName(
                    request);

            if (!string.IsNullOrWhiteSpace(
                    targetName))
            {
                string componentType =
                    ExtractTargetComponentType(
                        targetName,
                        request);

                string scenePath =
                    ExtractExplicitScenePath(
                        request) ??
                    ResolveNamedSceneAssetPath(
                        request) ??
                    ResolveUniqueScenePathForComponent(
                        componentType);

                EnsureExistingSceneLoadedForInspection(
                    scenePath);

                int resolvedId =
                    await FindUniqueGameObjectIdAsync(
                        runtime,
                        targetName,
                        componentType,
                        scenePath,
                        cancellationToken);

                if (resolvedId !=
                    0)
                {
                    return resolvedId;
                }


                throw new InvalidOperationException(
                    $"AIRuntime could not uniquely resolve the named GameObject '{targetName}'" +
                    (string.IsNullOrWhiteSpace(
                         componentType)
                        ? string.Empty
                        : $" with component '{componentType}'") +
                    (string.IsNullOrWhiteSpace(
                         scenePath)
                        ? " in the loaded Unity scenes."
                        : $" in scene '{scenePath}'."));
            }

            return ResolveSingleSelectedGameObjectId(
                liveSelectionEvidence);
        }


        private static int ResolveLoadedGameObjectId(
            string targetName,
            string componentType,
            string scenePath)
        {
            string normalizedScenePath =
                EditorCapabilityUtility.NormalizePath(
                    scenePath);

            GameObject[] matches =
                Resources.FindObjectsOfTypeAll<GameObject>()
                    .Where(
                        gameObject =>
                            gameObject !=
                            null &&
                            !EditorUtility.IsPersistent(
                                gameObject) &&
                            gameObject.scene.IsValid() &&
                            gameObject.scene.isLoaded &&
                            (string.IsNullOrWhiteSpace(
                                 targetName) ||
                             string.Equals(
                                 gameObject.name,
                                 targetName,
                                 StringComparison.OrdinalIgnoreCase)))
                    .Where(
                        gameObject =>
                            string.IsNullOrWhiteSpace(
                                normalizedScenePath) ||
                            string.Equals(
                                GetHierarchyTreeCapability
                                    .NormalizeScenePath(
                                        gameObject.scene),
                                normalizedScenePath,
                                StringComparison.OrdinalIgnoreCase))
                    .Where(
                        gameObject =>
                            string.IsNullOrWhiteSpace(
                                componentType) ||
                            gameObject
                                .GetComponents<Component>()
                                .Any(
                                    component =>
                                        component !=
                                        null &&
                                        (string.Equals(
                                             component.GetType().Name,
                                             componentType,
                                             StringComparison.Ordinal) ||
                                         string.Equals(
                                             component.GetType().FullName,
                                             componentType,
                                             StringComparison.Ordinal))))
                    .GroupBy(
                        gameObject =>
                            gameObject.GetInstanceID())
                    .Select(
                        group =>
                            group.First())
                    .Take(
                        2)
                    .ToArray();

            return matches.Length ==
                   1
                ? matches[0].GetInstanceID()
                : 0;
        }


        private static void EnsureExistingSceneLoadedForInspection(
            string scenePath)
        {
            if (string.IsNullOrWhiteSpace(
                    scenePath) ||
                GetHierarchyTreeCapability
                    .ResolveLoadedEditorScene(
                        scenePath)
                    .IsValid())
            {
                return;
            }


            SceneAsset sceneAsset =
                AssetDatabase.LoadAssetAtPath<SceneAsset>(
                    scenePath);

            if (sceneAsset ==
                null)
            {
                return;
            }


            UnityEditor.SceneManagement.EditorSceneManager
                .OpenScene(
                    scenePath,
                    UnityEditor.SceneManagement.OpenSceneMode.Additive);
        }


        private static async Task<int> FindUniqueGameObjectIdAsync(
            RuntimeFacade runtime,
            string targetName,
            string componentType,
            string scenePath,
            CancellationToken cancellationToken)
        {
            int directlyResolvedId =
                ResolveLoadedGameObjectId(
                    targetName,
                    componentType,
                    scenePath);

            if (directlyResolvedId !=
                0)
            {
                return directlyResolvedId;
            }

            // When the requested component is unique in the target scene,
            // its concrete type is stronger evidence than a descriptive or
            // stale object name.
            if (!string.IsNullOrWhiteSpace(
                    componentType))
            {
                int componentResolvedId =
                    ResolveLoadedGameObjectId(
                        null,
                        componentType,
                        scenePath);

                if (componentResolvedId !=
                    0)
                {
                    return componentResolvedId;
                }
            }


            string resultJson =
                await runtime.ExecuteCapabilityAsync(
                    "find_game_objects",
                    JsonConvert.SerializeObject(
                        new
                        {
                            name =
                                targetName,

                            exactName =
                                true,

                            componentType,

                            scenePath,

                            includeInactive =
                                true,

                            maxResults =
                                2
                        }),
                    cancellationToken);

            JObject result =
                JObject.Parse(
                    resultJson ??
                    "{}");

            JArray objects =
                result["Objects"] as JArray;

            return result["MatchCount"]?.Value<int>() ==
                       1 &&
                   objects?.Count ==
                       1
                ? objects[0]["InstanceId"]
                    ?.Value<int>() ??
                  0
                : 0;
        }


        private static string ExtractTargetComponentType(
            string targetName,
            string request)
        {
            string normalized =
                request ??
                string.Empty;

            HashSet<string> targetNameTokens =
                new HashSet<string>(
                    Regex.Matches(
                            targetName ??
                            string.Empty,
                            @"[A-Za-z_][A-Za-z0-9_]*",
                            RegexOptions.CultureInvariant)
                        .Cast<Match>()
                        .Select(match => match.Value),
                    StringComparer.OrdinalIgnoreCase);

            foreach (Match explicitComponent in
                     Regex.Matches(
                         normalized,
                         @"\b(?i:attach|add|has|with|configure|configured)\s+(?i:the\s+)?(?<type>[A-Z][A-Za-z0-9_]*)\b",
                         RegexOptions.CultureInvariant)
                         .Cast<Match>())
            {
                string candidate =
                    explicitComponent.Groups["type"]
                        .Value;

                // Phrases such as "configure Main Camera" and
                // "configure HoverDrone" name the target object, not a
                // component type. Continue scanning so a later clause such
                // as "has RequiredBehaviour" supplies the actual component.
                if (!targetNameTokens.Contains(
                        candidate) &&
                    !IsNonComponentTargetToken(
                        candidate))
                {
                    return candidate;
                }
            }


            HashSet<string> fileExtensions =
                new HashSet<string>(
                    new[]
                    {
                        "asset", "cs", "dll", "json", "mat", "meta",
                        "prefab", "shader", "txt", "unity", "uss", "uxml"
                    },
                    StringComparer.OrdinalIgnoreCase);

            foreach (Match serializedMemberOwner in
                     Regex.Matches(
                         normalized,
                         @"\b(?<type>[A-Z][A-Za-z0-9_]*)\s*\.\s*(?<member>[A-Za-z_][A-Za-z0-9_]*)\b",
                         RegexOptions.CultureInvariant)
                         .Cast<Match>())
            {
                if (!targetNameTokens.Contains(
                        serializedMemberOwner.Groups["type"]
                            .Value) &&
                    !fileExtensions.Contains(
                        serializedMemberOwner.Groups["member"]
                            .Value))
                {
                    return serializedMemberOwner.Groups["type"]
                        .Value;
                }
            }

            string[] mentionedScriptTypes =
                Regex.Matches(
                        normalized,
                        @"\b[A-Z][A-Za-z0-9_]*\b",
                        RegexOptions.CultureInvariant)
                    .Cast<Match>()
                    .Select(match => match.Value)
                    .Distinct(StringComparer.Ordinal)
                    .Where(
                        candidate =>
                            AssetDatabase.FindAssets(
                                    candidate +
                                    " t:MonoScript")
                                .Select(
                                    AssetDatabase.GUIDToAssetPath)
                                .Select(
                                    AssetDatabase.LoadAssetAtPath<MonoScript>)
                                .Any(
                                    script =>
                                        string.Equals(
                                            script?.GetClass()?.Name,
                                            candidate,
                                            StringComparison.Ordinal)))
                    .ToArray();

            return mentionedScriptTypes.Length ==
                   1
                ? mentionedScriptTypes[0]
                : null;
        }


        private static bool IsNonComponentTargetToken(
            string candidate)
        {
            return string.IsNullOrWhiteSpace(
                       candidate) ||
                   string.Equals(
                       candidate,
                       "X",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Y",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Z",
                       StringComparison.OrdinalIgnoreCase) ||
                   candidate.EndsWith(
                       "Material",
                       StringComparison.OrdinalIgnoreCase) ||
                   candidate.EndsWith(
                       "Scene",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "GameObject",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "All",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Any",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Existing",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Previous",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "Current",
                       StringComparison.OrdinalIgnoreCase);
        }


        private static string ResolveUniqueScenePathForComponent(
            string componentType)
        {
            if (string.IsNullOrWhiteSpace(
                    componentType))
            {
                return null;
            }


            string[] scriptPaths =
                AssetDatabase.FindAssets(
                        componentType +
                        " t:MonoScript")
                    .Select(
                        AssetDatabase.GUIDToAssetPath)
                    .Where(
                        path =>
                        {
                            MonoScript script =
                                AssetDatabase.LoadAssetAtPath<MonoScript>(
                                    path);

                            return string.Equals(
                                script?.GetClass()?.Name,
                                componentType,
                                StringComparison.Ordinal);
                        })
                    .Distinct(
                        StringComparer.OrdinalIgnoreCase)
                    .ToArray();

            if (scriptPaths.Length !=
                1)
            {
                return null;
            }


            string scriptPath =
                scriptPaths[0];

            string[] matchingScenes =
                AssetDatabase.FindAssets(
                        "t:Scene")
                    .Select(
                        AssetDatabase.GUIDToAssetPath)
                    .Where(
                        scenePath =>
                            AssetDatabase.GetDependencies(
                                    scenePath,
                                    false)
                                .Any(
                                    dependency =>
                                        string.Equals(
                                            dependency,
                                            scriptPath,
                                            StringComparison.OrdinalIgnoreCase)))
                    .Take(
                        2)
                    .ToArray();

            return matchingScenes.Length ==
                   1
                ? matchingScenes[0]
                : null;
        }


        private static string ExtractReviewGameObjectName(
            string request)
        {
            string normalized =
                RemoveSiblingDependencyClauses(
                    request);

            if (normalized.IndexOf(
                    "Main Camera",
                    StringComparison.OrdinalIgnoreCase) >=
                0)
            {
                return "Main Camera";
            }

            Match inspectionPhrase =
                Regex.Match(
                    normalized,
                    @"\b(?:inspect|using)\s+(?:the\s+)?(?<name>[A-Za-z_][A-Za-z0-9_.-]*(?:\s+[A-Za-z_][A-Za-z0-9_.-]*){0,3}?)\s+(?:GameObject|inspection)\b",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (inspectionPhrase.Success)
            {
                return ToPascalCaseIdentifier(
                    inspectionPhrase.Groups["name"]
                        .Value);
            }

            Match explicitGameObject =
                Regex.Match(
                    normalized,
                    @"\b(?:(?:GameObject\s+(?:named|called)\s+)|(?:on|to|select|inspect)\s+(?:the\s+)?)[`'""]?(?<name>[A-Za-z_][A-Za-z0-9_.-]*)(?:\s+GameObject)?",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (explicitGameObject.Success)
            {
                string explicitName =
                    NormalizeGameObjectNameCandidate(
                        explicitGameObject.Groups["name"]
                            .Value);

                if (!IsReservedWorkflowTargetName(
                        explicitName))
                {
                    return explicitName;
                }
            }

            Match componentOwner =
                Regex.Match(
                    normalized,
                    @"\b(?<name>[A-Z][A-Za-z0-9_.-]*)\s+(?i:has|contains|carries)\s+(?:an?\s+|the\s+)?[A-Z][A-Za-z0-9_]*\b",
                    RegexOptions.CultureInvariant);

            if (componentOwner.Success)
            {
                return componentOwner.Groups["name"]
                    .Value;
            }

            string createdName =
                ExtractExpectedCreatedGameObjectName(
                    normalized);

            if (!string.IsNullOrWhiteSpace(
                    createdName))
            {
                return createdName;
            }

            Match namedGameObject =
                Regex.Match(
                    normalized,
                    @"\b(?<name>[A-Z][A-Za-z0-9_.-]*)\s+GameObject\b",
                    RegexOptions.CultureInvariant);

            return namedGameObject.Success
                ? NormalizeGameObjectNameCandidate(
                    namedGameObject.Groups["name"].Value)
                : null;
        }


        private static string NormalizeGameObjectNameCandidate(
            string candidate)
        {
            return candidate?
                .Trim()
                .Trim('"', '\'', '`')
                .TrimEnd(
                    '.',
                    ',',
                    ';',
                    ':',
                    '!',
                    '?');
        }


        private static bool IsReservedWorkflowTargetName(
            string candidate)
        {
            return string.Equals(
                       candidate,
                       "sibling",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "task",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "card",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "parent",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "child",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "existing",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "jump",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "verify",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "confirm",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "fix",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "script",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "source",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "file",
                       StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(
                       candidate,
                       "code",
                       StringComparison.OrdinalIgnoreCase);
        }


        private static bool RequiresHierarchyTree(
            string request)
        {
            return
                !string.IsNullOrWhiteSpace(
                    request) &&
                request.IndexOf(
                    "get_hierarchy_tree",
                    StringComparison.OrdinalIgnoreCase) >=
                0;
        }


        private static bool RequiresSceneAuthoring(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return false;

            bool asksToCreate =
                request.IndexOf("create", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("build", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("author", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("set up", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("setup", StringComparison.OrdinalIgnoreCase) >= 0;

            bool refersToScene =
                request.IndexOf("scene", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf(".unity", StringComparison.OrdinalIgnoreCase) >= 0;

            bool refersToSceneObjects =
                request.IndexOf("GameObject", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("platform", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("ground", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("camera", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("goal", StringComparison.OrdinalIgnoreCase) >= 0;

            return asksToCreate &&
                   refersToScene &&
                   refersToSceneObjects;
        }


        private static bool RequiresGameplayReadinessReport(string request)
        {
            return Regex.IsMatch(request ?? "", @"\b(?:integration|gameplay)\b", RegexOptions.IgnoreCase) &&
                Regex.IsMatch(request ?? "", @"\b(?:verify|verification|test|review)\b", RegexOptions.IgnoreCase);
        }

        private static bool RequiresDirectoryScaffolding(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return false;

            bool asksToCreate =
                request.IndexOf("create", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("build", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("set up", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("setup", StringComparison.OrdinalIgnoreCase) >= 0;

            bool describesStructure =
                request.IndexOf("folder structure", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("subfolder", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("subfolders", StringComparison.OrdinalIgnoreCase) >= 0 ||
                request.IndexOf("directory structure", StringComparison.OrdinalIgnoreCase) >= 0;

            return asksToCreate &&
                   describesStructure;
        }


        private static bool IsStandaloneDirectoryScaffoldingTask(
            string request)
        {
            return RequiresDirectoryScaffolding(request) &&
                   !SplitReviewContractSentences(request)
                       .Any(IsSceneOrComponentMutationSentence);
        }


        private static string ExtractScaffoldingRoot(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return null;

            Match match =
                Regex.Match(
                    request,
                    @"(?<![A-Za-z0-9_])Assets[\\/](?<root>[^\\/\s\""'`,;:]+)",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);

            if (!match.Success)
                return null;

            string root =
                match.Groups["root"].Value
                    .Trim()
                    .TrimEnd(
                        '.',
                        ',',
                        ';',
                        ':',
                        '!',
                        '?');

            return string.IsNullOrWhiteSpace(root)
                ? null
                : root;
        }


        private static async Task EnsureScaffoldingRootAsync(
            RuntimeFacade runtime,
            string rootDirectoryName,
            CancellationToken cancellationToken)
        {
            if (runtime == null)
                throw new ArgumentNullException(nameof(runtime));

            if (string.IsNullOrWhiteSpace(rootDirectoryName) ||
                rootDirectoryName.IndexOf('/') >= 0 ||
                rootDirectoryName.IndexOf('\\') >= 0)
            {
                throw new InvalidOperationException(
                    "The explicit Assets scaffolding root is malformed.");
            }

            await runtime.ExecuteCapabilityAsync(
                "create_directory",
                JsonConvert.SerializeObject(
                    new
                    {
                        parentPath = "Assets",
                        directoryName = rootDirectoryName,
                        createParents = true
                    }),
                cancellationToken);
        }


        private static bool RequiresSelectedHierarchyTree(
            string request)
        {
            return RequiresHierarchyTree(
                       request) &&
                   RequiresEditorSelection(
                       request);
        }


        private static bool RequestsComponentFix(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            return request.IndexOf(
                       "fix",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "repair",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "add",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "attach",
                       StringComparison.OrdinalIgnoreCase) >=
                   0;
        }


        private static bool RequiresComponentRemovalVerification(
            string request)
        {
            return
                !string.IsNullOrWhiteSpace(
                    request) &&
                request.IndexOf(
                    "remove_editor_component",
                    StringComparison.OrdinalIgnoreCase) >=
                0;
        }


        private static int ResolveVerificationComponentId(
            string request,
            string componentInspectionEvidence)
        {
            JObject inspection =
                JObject.Parse(
                    componentInspectionEvidence ??
                    string.Empty);

            IEnumerable<JObject> components =
                (inspection["Components"] as JArray ??
                 new JArray())
                .OfType<JObject>()
                .Where(
                    component =>
                        component["InstanceId"]
                            ?.Value<int>() != 0 &&
                        !string.Equals(
                            component["Type"]
                                ?.Value<string>(),
                            "UnityEngine.Transform",
                            StringComparison.Ordinal));

            if (!string.IsNullOrWhiteSpace(request) &&
                request.IndexOf(
                    "Collider",
                    StringComparison.OrdinalIgnoreCase) >= 0)
            {
                components =
                    components.Where(
                        component =>
                            (component["Type"]
                                 ?.Value<string>() ??
                             string.Empty)
                            .EndsWith(
                                "Collider",
                                StringComparison.Ordinal));
            }

            int[] ids =
                components
                    .Select(
                        component =>
                            component["InstanceId"]
                                .Value<int>())
                    .Take(2)
                    .ToArray();

            if (ids.Length != 1)
            {
                throw new InvalidOperationException(
                    "Serialized-property verification requires exactly one disposable target component matching the card request.");
            }

            return ids[0];
        }


        private static SerializedBooleanVerification ResolveSerializedBooleanVerification(
            int componentInstanceId,
            string serializedPropertiesEvidence)
        {
            JObject result =
                JObject.Parse(
                    serializedPropertiesEvidence ??
                    string.Empty);

            JObject property =
                (result["Properties"] as JArray ??
                 new JArray())
                .OfType<JObject>()
                .FirstOrDefault(
                    candidate =>
                        string.Equals(
                            candidate["Path"]
                                ?.Value<string>(),
                            "m_IsTrigger",
                            StringComparison.Ordinal) &&
                        string.Equals(
                            candidate["Type"]
                                ?.Value<string>(),
                            "Boolean",
                            StringComparison.OrdinalIgnoreCase) &&
                        candidate["Editable"]
                            ?.Value<bool>() == true);

            if (property == null ||
                property["Value"] == null ||
                property["Value"].Type != JTokenType.Boolean)
            {
                throw new InvalidOperationException(
                    "The selected disposable component does not expose an editable Boolean m_IsTrigger property.");
            }

            bool initialValue =
                property["Value"].Value<bool>();

            string path =
                property["Path"].Value<string>();

            string initialJson =
                initialValue ? "true" : "false";

            string oppositeJson =
                initialValue ? "false" : "true";

            return new SerializedBooleanVerification
            {
                ComponentInstanceId = componentInstanceId,
                PropertyPath = path,
                InitialValue = initialValue,
                ToggleArguments = JsonConvert.SerializeObject(
                    new
                    {
                        componentInstanceId,
                        propertyPath = path,
                        valueJson = oppositeJson,
                        useExpectedValue = true,
                        expectedValueJson = initialJson
                    }),
                RestoreArguments = JsonConvert.SerializeObject(
                    new
                    {
                        componentInstanceId,
                        propertyPath = path,
                        valueJson = initialJson,
                        useExpectedValue = true,
                        expectedValueJson = oppositeJson
                    })
            };
        }


        private static bool IsMissingValueJsonFailure(
            string errorMessage)
        {
            return
                !string.IsNullOrWhiteSpace(errorMessage) &&
                errorMessage.IndexOf(
                    "ValueJson is required",
                    StringComparison.OrdinalIgnoreCase) >= 0;
        }


        private static string BuildSerializedBooleanRetryPrompt(
            SerializedBooleanVerification verification)
        {
            return
                "Retry the serialized-property verification now. The rejected call made no change because ValueJson was omitted. " +
                "Call set_serialized_property exactly twice in this order:\n1. " +
                verification.ToggleArguments +
                "\n2. " +
                verification.RestoreArguments +
                "\nDo not rename, omit, or unwrap any argument. Report success only if both results contain Verified=true.";
        }


        private static string AppendSerializedBooleanVerificationPrompt(
            string prompt,
            SerializedBooleanVerification verification)
        {
            if (verification == null)
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt ??
                    string.Empty);

            result.AppendLine();
            result.AppendLine(
                "AIRuntime has pre-read the requested disposable Boolean property. Call set_serialized_property exactly twice, in this order, using these complete arguments:");
            result.AppendLine(
                "1. " + verification.ToggleArguments);
            result.AppendLine(
                "2. " + verification.RestoreArguments);
            result.AppendLine(
                "ValueJson and ExpectedValueJson are JSON values encoded as strings in the tool arguments. Do not omit, rename, or unwrap either field. Both calls must return Verified=true; the second restores the original value.");

            return result.ToString();
        }


        private static async Task VerifyAndRestoreSerializedBooleanAsync(
            RuntimeFacade runtime,
            SerializedBooleanVerification verification,
            ToolResultCaptureObserver capture,
            CancellationToken cancellationToken)
        {
            string currentEvidence =
                await runtime.ExecuteCapabilityAsync(
                    "get_serialized_properties",
                    JsonConvert.SerializeObject(
                        new
                        {
                            componentInstanceId =
                                verification.ComponentInstanceId,
                            includeHidden = true,
                            maxProperties = 256
                        }),
                    cancellationToken);

            SerializedBooleanVerification current =
                ResolveSerializedBooleanVerification(
                    verification.ComponentInstanceId,
                    currentEvidence);

            if (current.InitialValue != verification.InitialValue)
            {
                string emergencyRestore =
                    JsonConvert.SerializeObject(
                        new
                        {
                            componentInstanceId =
                                verification.ComponentInstanceId,
                            propertyPath =
                                verification.PropertyPath,
                            valueJson =
                                verification.InitialValue
                                    ? "true"
                                    : "false",
                            useExpectedValue = true,
                            expectedValueJson =
                                current.InitialValue
                                    ? "true"
                                    : "false"
                        });

                await runtime.ExecuteCapabilityAsync(
                    "set_serialized_property",
                    emergencyRestore,
                    cancellationToken);
            }

            if (capture == null ||
                capture.Results.Count != 2)
            {
                throw new InvalidOperationException(
                    "The model did not complete exactly two set_serialized_property calls. AIRuntime restored the initial value when necessary.");
            }

            JObject first = JObject.Parse(capture.Results[0]);
            JObject second = JObject.Parse(capture.Results[1]);

            bool opposite = !verification.InitialValue;

            if (first["Verified"]?.Value<bool>() != true ||
                second["Verified"]?.Value<bool>() != true ||
                first["PreviousValue"]?.Value<bool>() != verification.InitialValue ||
                first["CurrentValue"]?.Value<bool>() != opposite ||
                second["PreviousValue"]?.Value<bool>() != opposite ||
                second["CurrentValue"]?.Value<bool>() != verification.InitialValue)
            {
                throw new InvalidOperationException(
                    "Serialized-property verification did not prove the expected guarded toggle-and-restore sequence.");
            }
        }


        private static string ResolveExactComponentRemovalArguments(
            string request,
            string componentInspectionEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    componentInspectionEvidence))
            {
                return null;
            }

            JObject inspection;

            try
            {
                inspection =
                    JObject.Parse(
                        componentInspectionEvidence);
            }
            catch (JsonException)
            {
                return null;
            }

            IEnumerable<JObject> components =
                (inspection["Components"] as JArray ??
                 new JArray())
                .OfType<JObject>()
                .Where(
                    component =>
                        component["InstanceId"]
                            ?.Value<int>() != 0 &&
                        !string.Equals(
                            component["Type"]
                                ?.Value<string>(),
                            "UnityEngine.Transform",
                            StringComparison.Ordinal));

            if (!string.IsNullOrWhiteSpace(
                    request) &&
                request.IndexOf(
                    "BoxCollider",
                    StringComparison.OrdinalIgnoreCase) >= 0)
            {
                components =
                    components.Where(
                        component =>
                            string.Equals(
                                component["Type"]
                                    ?.Value<string>(),
                                "UnityEngine.BoxCollider",
                                StringComparison.Ordinal) ||
                            string.Equals(
                                component["TypeName"]
                                    ?.Value<string>(),
                                "BoxCollider",
                                StringComparison.Ordinal));
            }

            JObject[] matches =
                components
                    .Take(2)
                    .ToArray();

            if (matches.Length !=
                1)
            {
                return null;
            }

            return JsonConvert.SerializeObject(
                new
                {
                    componentInstanceId =
                        matches[0]["InstanceId"]
                            .Value<int>()
                });
        }


        private static bool IsMissingComponentIdentityFailure(
            string errorMessage)
        {
            return
                !string.IsNullOrWhiteSpace(
                    errorMessage) &&
                errorMessage.IndexOf(
                    "Provide ComponentInstanceId",
                    StringComparison.OrdinalIgnoreCase) >= 0;
        }


        private static string BuildRemoveComponentRetryPrompt(
            string removalArguments)
        {
            return
                "Retry the required component-removal verification now. " +
                "Your previous call was rejected before any mutation because it omitted the component identity. " +
                "Call remove_editor_component exactly once using these arguments: " +
                removalArguments +
                ". Do not change or omit ComponentInstanceId. Report only the returned mutation result; AIRuntime will independently reinspect the affected GameObject.";
        }


        private static async Task<string> VerifyRemovedComponentAsync(
            RuntimeFacade runtime,
            string mutationResult,
            string preRemovalInspection,
            CancellationToken cancellationToken)
        {
            if (runtime ==
                null)
            {
                throw new ArgumentNullException(
                    nameof(runtime));
            }

            JObject mutation;

            try
            {
                mutation =
                    JObject.Parse(
                        mutationResult ??
                        string.Empty);
            }
            catch (JsonException exception)
            {
                throw new InvalidOperationException(
                    "AIRuntime could not parse the remove_editor_component result for verification.",
                    exception);
            }

            if (mutation["Succeeded"]?.Value<bool>() !=
                true)
            {
                throw new InvalidOperationException(
                    "remove_editor_component did not report Succeeded=true.");
            }

            int gameObjectInstanceId =
                mutation["GameObjectInstanceId"]
                    ?.Value<int>() ??
                0;

            if (gameObjectInstanceId ==
                0)
            {
                throw new InvalidOperationException(
                    "remove_editor_component did not return a GameObjectInstanceId for post-removal verification.");
            }

            string inspectionResult =
                await runtime.ExecuteCapabilityAsync(
                    "inspect_editor_component",
                    JsonConvert.SerializeObject(
                        new
                        {
                            gameObjectInstanceId,

                            includeSerializedProperties =
                                false
                        }),
                    cancellationToken);

            string verification =
                ValidateRemovedComponentInspection(
                mutationResult,
                inspectionResult);

            ValidateUnrelatedComponentsRemain(
                mutationResult,
                preRemovalInspection,
                inspectionResult);

            return verification +
                   " All unrelated pre-existing component instance IDs remain present.";
        }


        private static void ValidateUnrelatedComponentsRemain(
            string mutationResult,
            string preRemovalInspection,
            string postRemovalInspection)
        {
            if (string.IsNullOrWhiteSpace(
                    preRemovalInspection))
            {
                throw new InvalidOperationException(
                    "AIRuntime cannot verify unrelated components because the pre-removal inspection is unavailable.");
            }

            JObject mutation =
                JObject.Parse(
                    mutationResult);

            JObject before =
                JObject.Parse(
                    preRemovalInspection);

            JObject after =
                JObject.Parse(
                    postRemovalInspection);

            int removedComponentInstanceId =
                mutation["ComponentInstanceId"]
                    ?.Value<int>() ??
                0;

            HashSet<int> expectedRemainingIds =
                (before["Components"] as JArray ??
                 new JArray())
                .OfType<JObject>()
                .Select(
                    component =>
                        component["InstanceId"]
                            ?.Value<int>() ??
                        0)
                .Where(
                    id =>
                        id != 0 &&
                        id != removedComponentInstanceId)
                .ToHashSet();

            HashSet<int> actualIds =
                (after["Components"] as JArray ??
                 new JArray())
                .OfType<JObject>()
                .Select(
                    component =>
                        component["InstanceId"]
                            ?.Value<int>() ??
                        0)
                .Where(
                    id =>
                        id != 0)
                .ToHashSet();

            if (!expectedRemainingIds.IsSubsetOf(
                    actualIds))
            {
                throw new InvalidOperationException(
                    "Post-removal inspection found that an unrelated pre-existing component is missing.");
            }
        }


        private static string ValidateRemovedComponentInspection(
            string mutationResult,
            string inspectionResult)
        {
            JObject mutation =
                JObject.Parse(
                    mutationResult);

            JObject inspection =
                JObject.Parse(
                    inspectionResult);

            int gameObjectInstanceId =
                mutation["GameObjectInstanceId"]
                    ?.Value<int>() ??
                0;

            int removedComponentInstanceId =
                mutation["ComponentInstanceId"]
                    ?.Value<int>() ??
                0;

            string gameObjectName =
                mutation["GameObjectName"]
                    ?.Value<string>() ??
                "Unnamed";

            string componentType =
                mutation["ComponentType"]
                    ?.Value<string>() ??
                "Unknown component";

            int affectedCount =
                mutation["AffectedCount"]
                    ?.Value<int>() ??
                0;

            bool mutationVerified =
                mutation["VerificationSucceeded"]
                    ?.Value<bool>() ??
                false;

            JArray affectedGameObjects =
                mutation["AffectedGameObjects"] as
                    JArray ??
                new JArray();

            bool reportsAffectedGameObject =
                affectedGameObjects
                    .Values<string>()
                    .Any(
                        affected =>
                            string.Equals(
                                affected,
                                gameObjectName,
                                StringComparison.Ordinal));

            int inspectedGameObjectId =
                inspection["GameObjectInstanceId"]
                    ?.Value<int>() ??
                0;

            JArray components =
                inspection["Components"] as
                    JArray ??
                new JArray();

            bool removedInstanceStillExists =
                components
                    .OfType<JObject>()
                    .Any(
                        component =>
                            component["InstanceId"]
                                ?.Value<int>() ==
                            removedComponentInstanceId);

            bool transformRemains =
                components
                    .OfType<JObject>()
                    .Any(
                        component =>
                            string.Equals(
                                component["Type"]
                                    ?.Value<string>(),
                                "UnityEngine.Transform",
                                StringComparison.Ordinal));

            if (!mutationVerified ||
                affectedCount !=
                1 ||
                !reportsAffectedGameObject ||
                gameObjectInstanceId ==
                0 ||
                inspectedGameObjectId !=
                gameObjectInstanceId ||
                removedComponentInstanceId ==
                0 ||
                removedInstanceStillExists ||
                !transformRemains)
            {
                throw new InvalidOperationException(
                    $"Post-removal inspection could not verify that only component instance ID {removedComponentInstanceId} was removed from '{gameObjectName}'.");
            }

            return
                $"remove_editor_component reported AffectedCount=1 for '{gameObjectName}'. " +
                $"A fresh inspect_editor_component call confirmed that removed {componentType} instance ID {removedComponentInstanceId} is absent and UnityEngine.Transform remains.";
        }


        private static string BuildUnverifiableSelectionReview(
            string reason)
        {
            return
                "KANBAN_REVIEW: FAIL\n\n" +
                "AIRuntime could not perform a fresh component verification because the required live Unity selection is unavailable.\n\n" +
                "Evidence checked:\n" +
                "- get_editor_selection was called for this review.\n\n" +
                "Missing verification:\n" +
                $"- {reason}\n\n" +
                "Select exactly one target GameObject and reset the task to retry. The earlier implementation output has not been treated as fresh review evidence.";
        }


        private static int ResolveSingleSelectedGameObjectId(
            string liveSelectionEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    liveSelectionEvidence))
            {
                throw new InvalidOperationException(
                    "Component inspection requires a fresh Unity Editor selection. Select exactly one GameObject and retry.");
            }

            JObject root;

            try
            {
                root =
                    JObject.Parse(
                        liveSelectionEvidence);
            }
            catch (JsonException exception)
            {
                throw new InvalidOperationException(
                    "AIRuntime could not parse the authoritative Unity Editor selection result.",
                    exception);
            }

            JArray objects =
                root["Objects"] as
                    JArray ??
                root["objects"] as
                    JArray;

            if (objects ==
                null)
            {
                throw new InvalidOperationException(
                    "The Unity Editor selection result did not contain an Objects collection.");
            }

            var selectedGameObjects =
                objects
                    .OfType<JObject>()
                    .Select(
                        selectedObject =>
                            new
                            {
                                Id =
                                    selectedObject["GameObjectInstanceId"]
                                        ?.Value<int>() ??
                                    selectedObject["gameObjectInstanceId"]
                                        ?.Value<int>() ??
                                    0,

                                Name =
                                    selectedObject["Name"]
                                        ?.Value<string>() ??
                                    selectedObject["name"]
                                        ?.Value<string>() ??
                                    "Unnamed"
                            })
                    .Where(
                        selectedObject =>
                            selectedObject.Id !=
                            0)
                    .GroupBy(
                        selectedObject =>
                            selectedObject.Id)
                    .Select(
                        group =>
                            group.First())
                    .ToList();

            if (selectedGameObjects.Count ==
                1)
            {
                return selectedGameObjects[0].Id;
            }

            if (selectedGameObjects.Count ==
                0)
            {
                throw new InvalidOperationException(
                    "Component inspection requires exactly one selected GameObject. The current selection contains no GameObject target.");
            }

            throw new InvalidOperationException(
                "Component inspection requires exactly one selected GameObject. Current targets: " +
                string.Join(
                    ", ",
                    selectedGameObjects.Select(
                        selectedObject =>
                            selectedObject.Name)));
        }


        private static string ResolveRequestedFilePath(
            string request,
            string liveSelectionEvidence,
            string workspaceRoot)
        {
            string requestedPath =
                ExtractRequestedFilePath(
                    request);

            if (string.IsNullOrWhiteSpace(
                    requestedPath) ||
                string.IsNullOrWhiteSpace(
                    workspaceRoot))
            {
                return null;
            }

            requestedPath =
                requestedPath
                    .Replace(
                        '\\',
                        '/')
                    .TrimStart(
                        new[]
                        {
                            '/'
                        });

            List<string> candidates =
                new List<string>();

            if (requestedPath.IndexOf(
                    '/') >=
                0)
            {
                candidates.Add(
                    requestedPath);
            }
            else
            {
                string selectedFolderPath =
                    TryGetSelectedFolderPath(
                        liveSelectionEvidence);

                if (!string.IsNullOrWhiteSpace(
                        selectedFolderPath))
                {
                    candidates.Add(
                        selectedFolderPath.TrimEnd(
                            '/',
                            '\\') +
                        "/" +
                        requestedPath);
                }

                candidates.Add(
                    requestedPath);
            }

            foreach (
                string candidate
                in candidates.Distinct(
                    StringComparer.OrdinalIgnoreCase))
            {
                if (WorkspaceFileExists(
                        workspaceRoot,
                        candidate))
                {
                    return candidate;
                }
            }

            return FindUniqueWorkspaceFile(
                workspaceRoot,
                Path.GetFileName(
                    requestedPath));
        }


        private static IReadOnlyList<string> ResolveRequestedFilePaths(
            string request,
            string liveSelectionEvidence,
            string workspaceRoot,
            IReadOnlyCollection<string> structuredArtifactPaths)
        {
            List<string> paths =
                Regex.Matches(
                        request ??
                        string.Empty,
                        @"Assets(?:[\/][A-Za-z0-9_.-]+)+\.[A-Za-z0-9]+",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant)
                    .Cast<Match>()
                    .Select(
                        match =>
                            match.Value.Replace(
                                '\\',
                                '/'))
                    .Distinct(
                        StringComparer.OrdinalIgnoreCase)
                    .ToList();

            if (structuredArtifactPaths !=
                null)
            {
                var structuredScope =
                    new HashSet<string>(
                        structuredArtifactPaths
                            .Where(
                                path =>
                                    !string.IsNullOrWhiteSpace(
                                        path))
                            .Select(
                                path =>
                                    path.Trim()
                                        .Replace('\\', '/')),
                        StringComparer.OrdinalIgnoreCase);

                paths =
                    paths
                        .Where(
                            structuredScope.Contains)
                        .ToList();
            }

            if (paths.Count >
                0)
            {
                AppendOwningAssemblyDefinitionPaths(
                    paths,
                    workspaceRoot);

                return paths;
            }

            if (structuredArtifactPaths !=
                null)
            {
                // The child manifest is authoritative even when none of the
                // prose paths are in scope. Do not fall back to the first
                // filename mentioned in a prohibition or example.
                return Array.Empty<string>();
            }

            string path =
                ResolveRequestedFilePath(
                    request,
                    liveSelectionEvidence,
                    workspaceRoot);

            if (string.IsNullOrWhiteSpace(path))
                return new string[0];

            paths.Add(path);
            AppendOwningAssemblyDefinitionPaths(paths, workspaceRoot);
            return paths;
        }


        private static IReadOnlyCollection<string> ExtractCompletedDependencyArtifactPaths(string contextJson)
        {
            if (string.IsNullOrWhiteSpace(contextJson)) return Array.Empty<string>();
            JObject context = JObject.Parse(contextJson);
            return (context["CompletedDependencyArtifactPaths"] as JArray ?? new JArray())
                .Values<string>().Where(path => !string.IsNullOrWhiteSpace(path)).ToArray();
        }


        private static IReadOnlyCollection<string> ExtractStructuredReviewArtifactPaths(
            string kanbanTaskContextJson)
        {
            if (string.IsNullOrWhiteSpace(
                    kanbanTaskContextJson))
            {
                return null;
            }

            try
            {
                JObject context =
                    JObject.Parse(
                        kanbanTaskContextJson);

                if (context["IsChildTask"]?.Value<bool>() !=
                    true)
                {
                    // Direct/legacy cards may legitimately have no artifact
                    // manifest. Preserve their established prose fallback.
                    return null;
                }

                JProperty produced =
                    context.Property(
                        "ProducedArtifactPaths",
                        StringComparison.OrdinalIgnoreCase);

                JProperty consumed =
                    context.Property(
                        "ConsumedArtifactPaths",
                        StringComparison.OrdinalIgnoreCase);

                if (produced ==
                        null &&
                    consumed ==
                        null)
                {
                    return null;
                }

                return (produced?.Value as JArray ?? new JArray())
                    .Concat(
                        consumed?.Value as JArray ?? new JArray())
                    .Values<string>()
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(
                                path))
                    .Select(
                        path =>
                            path.Trim()
                                .Replace('\\', '/'))
                    .Distinct(
                        StringComparer.OrdinalIgnoreCase)
                    .ToArray();
            }
            catch (JsonException)
            {
                return null;
            }
        }


        private static void AppendOwningAssemblyDefinitionPaths(
            ICollection<string> paths,
            string workspaceRoot)
        {
            if (paths == null ||
                string.IsNullOrWhiteSpace(workspaceRoot) ||
                paths.Any(
                    path =>
                        path.EndsWith(
                            ".asmdef",
                            StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }

            string fullWorkspaceRoot =
                Path.GetFullPath(workspaceRoot)
                    .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

            string[] scriptPaths =
                paths
                    .Where(
                        path =>
                            path.EndsWith(
                                ".cs",
                                StringComparison.OrdinalIgnoreCase))
                    .ToArray();

            foreach (string scriptPath in scriptPaths)
            {
                string fullScriptPath =
                    Path.GetFullPath(
                        Path.Combine(
                            fullWorkspaceRoot,
                            scriptPath.Replace('/', Path.DirectorySeparatorChar)));

                if (!fullScriptPath.StartsWith(
                        fullWorkspaceRoot + Path.DirectorySeparatorChar,
                        StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                DirectoryInfo directory =
                    new FileInfo(fullScriptPath).Directory;

                while (directory != null &&
                       directory.FullName.StartsWith(
                           fullWorkspaceRoot + Path.DirectorySeparatorChar,
                           StringComparison.OrdinalIgnoreCase))
                {
                    if (!directory.Exists)
                    {
                        directory = directory.Parent;
                        continue;
                    }

                    string[] asmdefs =
                        Directory.GetFiles(
                            directory.FullName,
                            "*.asmdef",
                            SearchOption.TopDirectoryOnly);

                    if (asmdefs.Length > 0)
                    {
                        foreach (string asmdef in asmdefs.OrderBy(value => value, StringComparer.OrdinalIgnoreCase))
                        {
                            string relative =
                                asmdef.Substring(fullWorkspaceRoot.Length + 1)
                                    .Replace(Path.DirectorySeparatorChar, '/');

                            if (!paths.Contains(relative, StringComparer.OrdinalIgnoreCase))
                                paths.Add(relative);
                        }

                        break;
                    }

                    directory = directory.Parent;
                }
            }
        }


        private static string ExtractRequiredExistingDirectory(
            string request)
        {
            return ExtractRequiredExistingDirectories(request)
                .FirstOrDefault();
        }


        private static IReadOnlyList<string> ExtractRequiredExistingDirectories(
            string request)
        {
            if (string.IsNullOrWhiteSpace(request))
                return Array.Empty<string>();

            var paths =
                new List<string>();

            foreach (string sentence in SplitReviewContractSentences(request))
            {
                if (!Regex.IsMatch(
                        sentence,
                        @"\b(?:exist|exists|present)\b",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant))
                {
                    continue;
                }

                foreach (Match match in Regex.Matches(
                             sentence,
                             @"Assets(?:[\\/][A-Za-z0-9_.-]+)+",
                             RegexOptions.IgnoreCase |
                             RegexOptions.CultureInvariant))
                {
                    string path =
                        match.Value
                            .Replace('\\', '/')
                            .TrimEnd('.', ',', ';', ':');

                    if (Regex.IsMatch(
                            path,
                            @"\.(?:asmdef|cs|json|mat|md|meta|prefab|txt|unity)$",
                            RegexOptions.IgnoreCase |
                            RegexOptions.CultureInvariant) ||
                        paths.Contains(
                            path,
                            StringComparer.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    paths.Add(path);
                }
            }

            return paths;
        }


        private static string ExtractRequestedFilePath(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return null;
            }

            Match pathMatch =
                Regex.Match(
                    request,
                    @"(?<path>(?:[A-Za-z0-9_.-]+[\\/])+[A-Za-z0-9_.-]+\.[A-Za-z0-9]+)",
                    RegexOptions.CultureInvariant);

            if (pathMatch.Success)
            {
                return pathMatch
                    .Groups["path"]
                    .Value;
            }

            Match fileMatch =
                Regex.Match(
                    request,
                    @"(?<path>[A-Za-z0-9_.-]+\.[A-Za-z0-9]+)",
                    RegexOptions.CultureInvariant);

            return fileMatch.Success
                ? fileMatch
                    .Groups["path"]
                    .Value
                : null;
        }


        private static string ExtractExactAppendContent(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return null;
            }

            Match match =
                Regex.Match(
                    request,
                    "with\\s+exactly\\s+[\\\"'](?<content>.*?)[\\\"']",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant |
                    RegexOptions.Singleline);

            return match.Success
                ? match.Groups["content"]
                    .Value
                : null;
        }


        private static string ExtractAppendBaselineContent(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return null;
            }


            Match match =
                Regex.Match(
                    request,
                    "original\\s+text\\s*\\(\\s*[\\\"'](?<content>.*?)[\\\"']",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant |
                    RegexOptions.Singleline);


            if (!match.Success)
            {
                match =
                    Regex.Match(
                        request,
                        "confirms\\s+[\\\"'](?<content>.*?)[\\\"']\\s+followed",
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant |
                        RegexOptions.Singleline);
            }


            return match.Success
                ? match.Groups["content"].Value
                : null;
        }


        private static CopyVerificationPaths ResolveCopyVerificationPaths(
            string request,
            string workspaceRoot)
        {
            MatchCollection matches =
                Regex.Matches(
                    request ?? string.Empty,
                    "[\\\"'](?<path>Assets[\\\\/][^\\\"']+\\.[A-Za-z0-9]+)[\\\"']",
                    RegexOptions.IgnoreCase |
                    RegexOptions.CultureInvariant);


            if (matches.Count <
                2)
            {
                throw new InvalidOperationException(
                    "Copy verification requires quoted source and destination file paths.");
            }


            string sourcePath =
                matches[0].Groups["path"].Value
                    .Replace('\\', '/')
                    .Trim();

            string destinationPath =
                matches[1].Groups["path"].Value
                    .Replace('\\', '/')
                    .Trim();


            if (!WorkspaceFileExists(
                    workspaceRoot,
                    sourcePath))
            {
                sourcePath =
                    ResolveCopySourceNearDestination(
                        workspaceRoot,
                        destinationPath,
                        Path.GetFileName(
                            sourcePath));
            }


            if (string.IsNullOrWhiteSpace(
                    sourcePath) ||
                !WorkspaceFileExists(
                    workspaceRoot,
                    sourcePath))
            {
                throw new FileNotFoundException(
                    "Copy verification source file could not be resolved uniquely.");
            }


            return new CopyVerificationPaths
            {
                SourcePath = sourcePath,
                DestinationPath = destinationPath
            };
        }


        private static string ResolveCopySourceNearDestination(
            string workspaceRoot,
            string destinationPath,
            string sourceFileName)
        {
            if (!string.IsNullOrWhiteSpace(
                    destinationPath) &&
                !string.IsNullOrWhiteSpace(
                    sourceFileName))
            {
                string destinationDirectory =
                    Path.GetDirectoryName(
                            destinationPath.Replace(
                                '/',
                                Path.DirectorySeparatorChar))
                        ?.Replace(
                            Path.DirectorySeparatorChar,
                            '/');

                string adjacentSource =
                    string.IsNullOrWhiteSpace(
                        destinationDirectory)
                        ? sourceFileName
                        : destinationDirectory.TrimEnd(
                              '/',
                              '\\') +
                          "/" +
                          sourceFileName;

                if (WorkspaceFileExists(
                        workspaceRoot,
                        adjacentSource))
                {
                    return adjacentSource;
                }
            }

            return FindUniqueWorkspaceFile(
                workspaceRoot,
                sourceFileName);
        }


        private static async Task<string> VerifyCopiedFileAsync(
            RuntimeFacade runtime,
            CopyVerificationPaths paths,
            CancellationToken cancellationToken)
        {
            string sourceContent =
                await runtime.ExecuteCapabilityAsync(
                    "read_file",
                    JsonConvert.SerializeObject(
                        new
                        {
                            path = paths.SourcePath
                        }),
                    cancellationToken);

            string destinationContent =
                await runtime.ExecuteCapabilityAsync(
                    "read_file",
                    JsonConvert.SerializeObject(
                        new
                        {
                            path = paths.DestinationPath
                        }),
                    cancellationToken);

            bool identical =
                string.Equals(
                    sourceContent,
                    destinationContent,
                    StringComparison.Ordinal);


            if (!identical)
            {
                throw new InvalidOperationException(
                    "copy_file verification failed because source and destination contents differ.");
            }


            return JsonConvert.SerializeObject(
                new
                {
                    paths.SourcePath,
                    paths.DestinationPath,
                    SourceExists = true,
                    DestinationExists = true,
                    ContentsIdentical = true,
                    SourceReadResult = sourceContent,
                    DestinationReadResult = destinationContent
                },
                Formatting.Indented);
        }


        private static string AppendCopyVerificationEvidence(
            string prompt,
            CopyVerificationPaths paths,
            string evidence,
            bool reviewMode)
        {
            if (paths == null)
            {
                return prompt;
            }


            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ??
                    string.Empty);

            result.AppendLine();
            result.AppendLine();


            if (!reviewMode)
            {
                result.AppendLine(
                    "COPY VERIFICATION: Call copy_file exactly once with these resolved arguments:");
                result.AppendLine(
                    JsonConvert.SerializeObject(
                        new
                        {
                            sourcePath = paths.SourcePath,
                            destinationPath = paths.DestinationPath
                        }));
                result.AppendLine(
                    "AIRuntime will independently read and compare both files after the copy. Do not call read_file yourself.");
            }
            else
            {
                result.AppendLine(
                    "AIRuntime independently read both files immediately before review. Treat this as authoritative copy verification evidence:");
                result.AppendLine(
                    evidence ??
                    string.Empty);
            }


            return result.ToString();
        }


        private static string AppendGeneralReviewEvidence(
            string prompt,
            string compilationEvidence,
            string directoryExistenceEvidence,
            bool reviewMode)
        {
            if (!reviewMode ||
                (string.IsNullOrWhiteSpace(
                     compilationEvidence) &&
                 string.IsNullOrWhiteSpace(
                     directoryExistenceEvidence)))
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ??
                    string.Empty);

            if (!string.IsNullOrWhiteSpace(
                    compilationEvidence))
            {
                result.AppendLine();
                result.AppendLine();
                result.AppendLine(
                    "AIRuntime called get_compilation_errors immediately before review. Treat this result as authoritative:");
                result.AppendLine(
                    compilationEvidence.Trim());

                if (compilationEvidence.IndexOf(
                        "\"Scope\": \"TargetArtifacts\"",
                        StringComparison.Ordinal) >= 0 ||
                    compilationEvidence.IndexOf(
                        "\"Scope\":\"TargetArtifacts\"",
                        StringComparison.Ordinal) >= 0)
                {
                    result.AppendLine();
                    result.AppendLine(
                        "ARTIFACT-SCOPED COMPILATION RULE: This card owns only TargetArtifactPaths. PASS/FAIL must use the scoped ErrorCount and Messages. UnrelatedDiagnostics belong to other cards and cannot fail this card. Do not fail merely because the implementation report made a broader project-health claim when the requested deliverable and its owned artifacts pass; report that broader statement as a non-blocking correction. Project-wide zero-error health is enforced by its own integration card.");
                }
            }

            if (!string.IsNullOrWhiteSpace(
                    directoryExistenceEvidence))
            {
                result.AppendLine();
                result.AppendLine();
                result.AppendLine(
                    "AIRuntime called get_file_info for every directory whose existence is required by the card. Treat AllRequiredDirectoriesExist=true and each ExistsAsDirectory=true as authoritative directory evidence; file_exists is file-only and must not be used to judge a directory:");
                result.AppendLine(
                    directoryExistenceEvidence.Trim());
            }

            result.AppendLine();
            result.AppendLine(
                "A negative scope criterion such as 'no files outside the requested folder were modified' must be judged from actual contrary evidence or a captured mutation record. Do not fail solely because an after-the-fact read-only review cannot prove a negative when no outside mutation is reported or observed.");

            return result.ToString();
        }


        private static string BuildReviewEvidenceBundle(
            string kanbanTaskContextEvidence,
            string liveSelectionEvidence,
            string rootFileEvidence,
            string fileContentEvidence,
            string componentInspectionEvidence,
            string compilationEvidence,
            string directoryExistenceEvidence,
            string materialAssetEvidence,
            string hierarchyTreeEvidence,
            ToolResultCaptureObserver componentInspectionCapture,
            ToolResultCaptureObserver sceneInspectionCapture,
            ToolResultCaptureObserver fileReadCapture,
            ToolResultCaptureObserver directoryTreeCapture)
        {
            JObject bundle =
                new JObject
                {
                    ["SchemaVersion"] = 1,
                    ["CapturedAtUtc"] = DateTime.UtcNow.ToString("O"),
                    ["KanbanTaskContext"] = ToEvidenceToken(kanbanTaskContextEvidence),
                    ["LiveSelection"] = ToEvidenceToken(liveSelectionEvidence),
                    ["RootFiles"] = ToEvidenceToken(rootFileEvidence),
                    ["FileContents"] = ToEvidenceToken(fileContentEvidence),
                    ["ComponentInspection"] = ToEvidenceToken(componentInspectionEvidence),
                    ["Compilation"] = ToEvidenceToken(compilationEvidence),
                    ["DirectoryExistence"] = ToEvidenceToken(directoryExistenceEvidence),
                    ["Materials"] = ToEvidenceToken(materialAssetEvidence),
                    ["Hierarchy"] = ToEvidenceToken(hierarchyTreeEvidence),
                    ["ReviewerToolCalls"] =
                        new JObject
                        {
                            ["inspect_editor_component"] =
                                ToEvidenceArray(componentInspectionCapture),
                            ["inspect_scene"] =
                                ToEvidenceArray(sceneInspectionCapture),
                            ["read_file"] =
                                ToEvidenceArray(fileReadCapture),
                            ["list_directory_tree"] =
                                ToEvidenceArray(directoryTreeCapture)
                        }
                };

            return bundle.ToString(Formatting.None);
        }


        private static JToken ToEvidenceToken(
            string raw)
        {
            if (string.IsNullOrWhiteSpace(raw))
                return JValue.CreateNull();

            try
            {
                return JToken.Parse(raw);
            }
            catch (JsonReaderException)
            {
                return raw;
            }
        }


        private static JObject BuildMissingReviewArtifactEvidence(
            string path,
            string error)
        {
            return new JObject
            {
                ["Succeeded"] = false,
                ["IsError"] = true,
                ["FailureKind"] = "MissingArtifact",
                ["Path"] = path ?? string.Empty,
                ["Error"] = error ?? "The declared artifact was not found.",
                ["ReviewInstruction"] =
                    "Treat this as authoritative missing-artifact evidence and return an ImplementationDefect verdict. Do not crash the review runner."
            };
        }


        private static JArray ToEvidenceArray(
            ToolResultCaptureObserver capture)
        {
            JArray results =
                new JArray();

            if (capture == null)
                return results;

            foreach (string raw in capture.Results)
            {
                results.Add(ToEvidenceToken(raw));
            }

            return results;
        }


        private static Task<string> CaptureMaterialAssetReviewEvidenceAsync(
            RuntimeFacade runtime,
            string request,
            CancellationToken cancellationToken)
        {
            // Material evidence is captured directly from Unity objects on
            // the Editor thread. Keep the runner-shaped signature so this
            // remains interchangeable with the other evidence collectors.
            _ = runtime;
            _ = cancellationToken;

            JArray assets =
                new JArray();

            foreach (string assetPath in ExtractRequestedMaterialAssetPaths(request))
            {
                Material material =
                    AssetDatabase.LoadAssetAtPath<Material>(
                        assetPath);

                if (material == null)
                {
                    assets.Add(
                        new JObject
                        {
                            ["AssetPath"] = assetPath,
                            ["Exists"] = false,
                            ["ObjectType"] = null
                        });

                    continue;
                }

                Color color =
                    material.HasProperty("_BaseColor")
                        ? material.GetColor("_BaseColor")
                        : material.HasProperty("_Color")
                            ? material.GetColor("_Color")
                            : material.color;

                bool hasNonDefaultColor =
                    Mathf.Abs(color.r - Color.white.r) > 0.0001f ||
                    Mathf.Abs(color.g - Color.white.g) > 0.0001f ||
                    Mathf.Abs(color.b - Color.white.b) > 0.0001f ||
                    Mathf.Abs(color.a - Color.white.a) > 0.0001f;

                assets.Add(
                    new JObject
                    {
                        ["AssetPath"] = assetPath,
                        ["Exists"] = true,
                        ["ObjectType"] = typeof(Material).FullName,
                        ["InstanceId"] = material.GetInstanceID(),
                        ["ShaderName"] = material.shader != null
                            ? material.shader.name
                            : string.Empty,
                        ["ShaderSupported"] =
                            material.shader != null &&
                            material.shader.isSupported,
                        ["Color"] = new JObject
                        {
                            ["r"] = color.r,
                            ["g"] = color.g,
                            ["b"] = color.b,
                            ["a"] = color.a
                        },
                        ["DefaultColor"] = new JObject
                        {
                            ["r"] = 1.0f,
                            ["g"] = 1.0f,
                            ["b"] = 1.0f,
                            ["a"] = 1.0f
                        },
                        ["HasNonDefaultColor"] = hasNonDefaultColor,
                        ["EmissionEnabled"] =
                            material.IsKeywordEnabled("_EMISSION")
                    });
            }

            return Task.FromResult(
                new JObject
                {
                    ["RequestedMaterialCount"] = assets.Count,
                    ["Materials"] = assets
                }.ToString(Formatting.Indented));
        }


        private static string AppendMaterialAssetReviewEvidence(
            string prompt,
            string materialAssetEvidence,
            bool reviewMode)
        {
            if (!reviewMode ||
                string.IsNullOrWhiteSpace(materialAssetEvidence))
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ?? string.Empty);

            result.AppendLine();
            result.AppendLine();
            result.AppendLine(
                "AIRuntime inspected each explicitly requested Material asset immediately before review. Treat this as authoritative persistent-asset evidence. Exists=true with ObjectType=UnityEngine.Material proves the asset and exact path. Color and HasNonDefaultColor are read from the live Material using its shader's _BaseColor or _Color property; do not demand GameObject selection or component inspection for a standalone material card:");
            result.AppendLine(
                materialAssetEvidence.Trim());
            result.AppendLine();
            result.AppendLine(
                "A negative scope criterion such as 'no files outside the requested folder were modified' must be judged from actual contrary evidence or a captured mutation record. Do not fail solely because an after-the-fact read-only review cannot prove a negative when no outside mutation is reported or observed.");

            return result.ToString();
        }


        private static async Task<string> VerifyAppendedFileAsync(
            RuntimeFacade runtime,
            string path,
            string baseline,
            string appendedContent,
            bool requireMatch,
            CancellationToken cancellationToken)
        {
            if (string.IsNullOrWhiteSpace(
                    path) ||
                baseline ==
                null ||
                appendedContent ==
                null)
            {
                throw new InvalidOperationException(
                    "Append verification requires an exact target path, baseline, and appended content.");
            }

            string actualContent =
                await runtime.ExecuteCapabilityAsync(
                    "read_file",
                    JsonConvert.SerializeObject(
                        new
                        {
                            path
                        }),
                    cancellationToken);

            string separator =
                baseline.Length >
                    0 &&
                appendedContent.Length >
                    0 &&
                baseline[baseline.Length -
                         1] !=
                    '\r' &&
                baseline[baseline.Length -
                         1] !=
                    '\n' &&
                appendedContent[0] !=
                    '\r' &&
                appendedContent[0] !=
                    '\n'
                    ? "\n"
                    : string.Empty;

            string expectedContent =
                baseline +
                separator +
                appendedContent;

            bool matches =
                string.Equals(
                    expectedContent,
                    actualContent,
                    StringComparison.Ordinal);

            if (requireMatch &&
                !matches)
            {
                throw new InvalidOperationException(
                    "append_file verification failed because the final contents do not equal the baseline followed by exactly one appended value.");
            }

            return JsonConvert.SerializeObject(
                new
                {
                    Path = path,
                    Baseline = baseline,
                    AppendedContent = appendedContent,
                    ExpectedContent = expectedContent,
                    ActualContent = actualContent,
                    ContentsMatchExactly = matches,
                    AppendOccurrenceCount =
                        CountExactOccurrences(
                            actualContent,
                            appendedContent)
                },
                Formatting.Indented);
        }


        private static string AppendAppendVerificationEvidence(
            string prompt,
            string path,
            string baseline,
            string appendedContent,
            string evidence,
            bool reviewMode)
        {
            if (string.IsNullOrWhiteSpace(
                    path))
            {
                return prompt;
            }

            StringBuilder result =
                new StringBuilder(
                    prompt?.TrimEnd() ??
                    string.Empty);

            result.AppendLine();
            result.AppendLine();

            if (!reviewMode)
            {
                result.AppendLine(
                    "APPEND VERIFICATION: AIRuntime already reset the target file to the baseline. Call append_file exactly once with:");
                result.AppendLine(
                    JsonConvert.SerializeObject(
                        new
                        {
                            path,
                            content = appendedContent
                        }));
                result.AppendLine(
                    "Do not call write_file or read_file. AIRuntime owns the final content read and exact comparison.");
            }
            else
            {
                result.AppendLine(
                    "AIRuntime independently read the append target immediately before review. Treat this as authoritative append verification evidence:");
                result.AppendLine(
                    evidence ??
                    string.Empty);
            }

            return result.ToString();
        }


        private static int CountExactOccurrences(
            string value,
            string search)
        {
            if (string.IsNullOrEmpty(
                    value) ||
                string.IsNullOrEmpty(
                    search))
            {
                return 0;
            }

            int count =
                0;

            int index =
                0;

            while ((index = value.IndexOf(
                        search,
                        index,
                        StringComparison.Ordinal)) >=
                   0)
            {
                count++;
                index +=
                    search.Length;
            }

            return count;
        }


        private static bool IsMissingAppendContentFailure(
            string errorMessage)
        {
            return
                !string.IsNullOrWhiteSpace(
                    errorMessage) &&
                errorMessage.IndexOf(
                    "Content is required",
                    StringComparison.OrdinalIgnoreCase) >=
                0;
        }


        private static string BuildAppendFileRetryPrompt(
            string path,
            string content)
        {
            string arguments =
                JsonConvert.SerializeObject(
                    new
                    {
                        path,
                        content
                    });

            return
                "The append_file call was rejected before any file mutation because its required content argument was missing. " +
                "Retry once now. Call append_file with exactly this JSON object: " +
                arguments +
                ". Do not rename or omit content. After append_file succeeds, call read_file with the same path and report only the verified result.";
        }


        private static string TryGetSelectedFolderPath(
            string liveSelectionEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    liveSelectionEvidence))
            {
                return null;
            }

            try
            {
                JObject root =
                    JObject.Parse(
                        liveSelectionEvidence);

                JArray objects =
                    root["Objects"] as
                        JArray ??
                    root["objects"] as
                        JArray;

                return objects?
                    .OfType<JObject>()
                    .Where(
                        selectedObject =>
                            selectedObject["IsFolder"]?.Value<bool>() ??
                            selectedObject["isFolder"]?.Value<bool>() ??
                            false)
                    .Select(
                        selectedObject =>
                            selectedObject["FolderPath"]?.Value<string>() ??
                            selectedObject["folderPath"]?.Value<string>())
                    .FirstOrDefault(
                        path =>
                            !string.IsNullOrWhiteSpace(
                                path));
            }
            catch (JsonException)
            {
                return null;
            }
        }


        private static bool WorkspaceFileExists(
            string workspaceRoot,
            string relativePath)
        {
            if (HasMalformedPathSegments(
                    relativePath))
            {
                return false;
            }

            string normalizedRoot =
                Path.GetFullPath(
                    workspaceRoot)
                    .TrimEnd(
                        Path.DirectorySeparatorChar,
                        Path.AltDirectorySeparatorChar);

            string fullPath =
                Path.GetFullPath(
                    Path.Combine(
                        normalizedRoot,
                        relativePath.Replace(
                            '/',
                            Path.DirectorySeparatorChar)));

            string containmentPrefix =
                normalizedRoot +
                Path.DirectorySeparatorChar;

            return fullPath.StartsWith(
                       containmentPrefix,
                       StringComparison.OrdinalIgnoreCase) &&
                   File.Exists(
                       fullPath);
        }


        private static bool HasMalformedPathSegments(
            string path)
        {
            if (string.IsNullOrWhiteSpace(
                    path))
            {
                return true;
            }

            string[] segments =
                path.Split(
                    new[]
                    {
                        '/',
                        '\\'
                    },
                    StringSplitOptions.None);

            return segments.Any(
                segment =>
                    string.IsNullOrWhiteSpace(
                        segment) ||
                    !string.Equals(
                        segment,
                        segment.Trim(),
                        StringComparison.Ordinal));
        }


        private static string FindUniqueWorkspaceFile(
            string workspaceRoot,
            string fileName)
        {
            if (string.IsNullOrWhiteSpace(
                    fileName))
            {
                return null;
            }

            string normalizedRoot =
                Path.GetFullPath(
                    workspaceRoot)
                    .TrimEnd(
                        Path.DirectorySeparatorChar,
                        Path.AltDirectorySeparatorChar);

            List<string> matches =
                new List<string>();

            foreach (
                string searchRootName
                in new[]
                {
                    "Assets",
                    "Packages",
                    "ProjectSettings",
                    "UserSettings"
                })
            {
                string searchRoot =
                    Path.Combine(
                        normalizedRoot,
                        searchRootName);

                if (!Directory.Exists(
                        searchRoot))
                {
                    continue;
                }

                foreach (
                    string match
                    in Directory.EnumerateFiles(
                        searchRoot,
                        fileName,
                        SearchOption.AllDirectories))
                {
                    matches.Add(
                        match);

                    if (matches.Count >
                        1)
                    {
                        return null;
                    }
                }
            }

            if (matches.Count !=
                1)
            {
                return null;
            }

            return matches[0]
                .Substring(
                    normalizedRoot.Length)
                .TrimStart(
                    Path.DirectorySeparatorChar,
                    Path.AltDirectorySeparatorChar)
                .Replace(
                    Path.DirectorySeparatorChar,
                    '/');
        }


        private static void AddCapabilityIfMissing(
            ICollection<string> capabilities,
            string capabilityName)
        {
            if (capabilities.Any(
                    value =>
                        string.Equals(
                            value?.Trim(),
                            capabilityName,
                            StringComparison.Ordinal)))
            {
                return;
            }

            capabilities.Add(
                capabilityName);
        }


        private static void ConfigureComponentRemovalCapability(
            IList<string> capabilities,
            bool authorized)
        {
            if (capabilities == null ||
                authorized)
            {
                return;
            }

            for (int index = capabilities.Count - 1;
                 index >= 0;
                 index--)
            {
                if (string.Equals(
                        capabilities[index]?.Trim(),
                        "remove_editor_component",
                        StringComparison.Ordinal))
                {
                    capabilities.RemoveAt(index);
                }
            }
        }


        private static bool RequestsAllSelectedMissingScriptRemoval(
            string handoffMessage)
        {
            if (string.IsNullOrWhiteSpace(
                    handoffMessage))
            {
                return false;
            }


            string normalized =
                handoffMessage.Trim();

            return normalized.IndexOf(
                       "remove",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 &&
                   (normalized.IndexOf(
                        "missing components",
                        StringComparison.OrdinalIgnoreCase) >=
                    0 ||
                    normalized.IndexOf(
                        "missing scripts",
                        StringComparison.OrdinalIgnoreCase) >=
                    0);
        }


        private static string BuildReviewPrompt(
            string title,
            string description,
            string acceptanceCriteria,
            string agentOutput,
            string handoffMessage,
            string liveSelectionEvidence,
            string rootFileEvidence,
            string fileContentPath,
            string fileContentEvidence,
            string componentInspectionEvidence,
            string effectiveRequest)
        {
            StringBuilder prompt =
                new StringBuilder();

            string verifiedCapability =
                ExtractVerifiedCapabilityName(
                    effectiveRequest);

            string scopedHandoffMessage =
                SanitizeReviewHandoffEvidenceClaims(
                    handoffMessage,
                    effectiveRequest,
                    fileContentEvidence,
                    componentInspectionEvidence);

            prompt.AppendLine(
                "Review the completed Kanban task against the current active request.");

            prompt.AppendLine(
                $"UNITY PROJECT VERSION: This workspace runs Unity {Application.unityVersion}. Judge APIs against this exact version.");

            prompt.AppendLine(
                "Inspect the actual workspace using available read tools where necessary.");

            prompt.AppendLine(
                "Check every acceptance criterion and do not approve unsupported claims.");

            prompt.AppendLine(
                "FINAL REVIEW ROUTING: If the approved plan is sound but this implementation is wrong or incomplete, return FAIL with [ReviewFailureKind: ImplementationDefect]. If the graph itself is incomplete, contradictory, has incorrect ownership/dependencies, or contains a card too large to implement and track atomically, return FAIL with [ReviewFailureKind: PlanningDefect]. Planning defects return the whole project to Planning; do not create cards or modify product state during review.");

            prompt.AppendLine(
                "UPSTREAM ARTIFACT ROUTING: When this card fails solely because one exact ConsumedArtifactPath owned by another card is currently defective, include one standalone line in the form [UpstreamArtifactDefect: Assets/exact/path.ext]. Use the exact path from the structured contract and report only the first blocking upstream artifact. Do not use this marker for this card's ProducedArtifactPaths or for a scene it may mutate through an exact scene:... MutatedResourceKey; those are local implementation defects. AIRuntime validates the path and reopens only its canonical owner.");

            prompt.AppendLine(
                "ARTIFACT EVIDENCE RULE: ProducedArtifactPaths is a coordination contract for delegated child cards, not an automatic delivery manifest for a card executed directly. When the scoped Kanban metadata reports IsChildTask=false and DirectChildCount=0, an empty ProducedArtifactPaths list is not evidence that no files or scenes were created; inspect the workspace and Unity state instead.");

            prompt.AppendLine(
                "CHILD ARTIFACT SCOPE RULE: For a delegated child card, For source-only ownership AIRuntime pre-reads every declared source output, whether or not its exact path appears in prose. Other card types use paths present in both prose and the structured artifact contract. The embedded file evidence is the authoritative review scope. A filename appearing only in a prohibition, negative example, or alternative (for example, 'do not create X') is not a required artifact and must never be reported as missing work.");

            prompt.AppendLine(
                "CARD OWNERSHIP RULE: Judge this card on its own Description and AcceptanceCriteria. Ancestor contracts contribute shared invariants; they do not make every child responsible for every parent feature. Use SiblingContracts from the scoped Kanban context to identify work allocated to other cards. Do not fail this card for an unfinished sibling's explicitly owned script, behavior, prefab, UI logic, or final verification, and do not invent quality requirements absent from this card (for example a particular CanvasScaler mode). Those outcomes are enforced when their owner and the final integration card run.");

            prompt.AppendLine(
                "RELATIONAL ACCEPTANCE RULE: For claims such as reachable, connected, follows, overlaps, compatible, ordered, or playable, checking each participant independently is insufficient. Compare the authoritative values that connect them (such as movement or data direction, constraints, positions or bounds, references, and producer-consumer formats). A contradiction between individually valid child outputs is a FAIL even when every child previously passed review.");

            prompt.AppendLine(
                "OBJECT IDENTITY RULE: Every inspect_editor_component result is scoped only to its returned GameObjectInstanceId, GameObjectName, and HierarchyPath. When a criterion names an object, use the result whose identity matches that object exactly. Never attribute a component or property from Player, a prefab, or another object to Main Camera merely because both objects contain the same component type.");

            prompt.AppendLine(
                "CURRENT-STATE RULE: A review snapshot establishes current state, not whether a component existed before the implementation. Do not invent before/after history. Dirty=false from authoritative inspect_scene means the scene is currently saved; it is not evidence that requested saved changes were never made.");

            prompt.AppendLine(
                "UNITY ENUM RULE: For an authoritative Enum property object, the returned name is the semantic value and index is its zero-based Inspector choice index, not necessarily the enum's underlying integer. Prefer the returned name and the owning object's inspection display value.");

            if (!string.IsNullOrWhiteSpace(
                    scopedHandoffMessage))
            {
                prompt.AppendLine();

                bool systemReviewHandoff =
                    IsSystemGeneratedReviewHandoff(
                        scopedHandoffMessage);

                prompt.AppendLine(
                    systemReviewHandoff
                        ? "PRIOR REVIEW FINDINGS (context only; these cannot add requirements or override the original card):"
                        : "CURRENT ACTIVE REQUEST (this replaces the original card request for this review):");

                prompt.AppendLine(
                    scopedHandoffMessage.Trim());

                prompt.AppendLine();
                prompt.AppendLine(
                    systemReviewHandoff
                        ? "The original card and acceptance criteria are the sole authority. Disregard any prior finding that demands evidence, mutation, or acceptance conditions outside that scope."
                        : "The original card and acceptance criteria remain authoritative context and apply where relevant to this current request.");

                prompt.AppendLine(
                    "Judge whether the implementation completed this request. Merely acknowledging or repeating it is a FAIL.");
            }

            if (!string.IsNullOrWhiteSpace(
                    liveSelectionEvidence))
            {
                prompt.AppendLine(
                    "AIRuntime captured the following authoritative get_editor_selection result immediately before this review. Treat it as the current state. Do not claim that missing components remain when this snapshot reports zero:");

                prompt.AppendLine();
                prompt.AppendLine(
                    liveSelectionEvidence.Trim());
            }

            if (!string.IsNullOrWhiteSpace(
                    rootFileEvidence))
            {
                prompt.AppendLine(
                    "AIRuntime captured the following authoritative list_directory result for path=\".\", entryType=\"Files\", and outputFormat=\"PathsOnly\" immediately before this review. Every returned value is a root-level file path, including filenames containing periods. Compare the implementation output with this complete snapshot:");

                prompt.AppendLine();
                prompt.AppendLine(
                    rootFileEvidence.Trim());

                prompt.AppendLine();
                prompt.AppendLine(
                    "For this exact-list task, PASS requires the implementation output to contain every returned Path exactly once, one plain path per line, with no heading, numbering, description, metadata, summary, or additional prose.");
            }

            if (RequiresEditorSelection(
                    effectiveRequest))
            {
                prompt.AppendLine(
                    "This is a live-selection task. Call get_editor_selection now and compare the implementation output with the fresh result. Missing-script validation applies only to Objects where IsGameObject=true.");

                if (!string.Equals(
                        verifiedCapability,
                        "inspect_game_object",
                        StringComparison.Ordinal))
                {
                    prompt.AppendLine(
                        "If any selected GameObject has HasMissingComponents=true or MissingComponentCount greater than zero, the verdict must be FAIL. Reporting or acknowledging [Missing Script] is accurate evidence, but it does not resolve the defect and must never produce PASS. PASS is allowed only after a fresh selection result confirms that every selected GameObject has zero missing components.");
                }

                prompt.AppendLine(
                    "MissingComponentCount=0 or HasMissingComponents=false confirms that a GameObject is clean. For non-GameObject assets such as TextAsset files, component and missing-script fields are not applicable and must not be used as failure criteria. Do not require an explicit statement that a clean object has no missing components unless the original request asks for it.");

                prompt.AppendLine(
                    "For a selected Project-window folder where IsFolder=true, component checks are not applicable. If the task asks to list files in that folder, verify FolderPath with list_directory using entryType=\"Files\", outputFormat=\"PathsOnly\", and includeMetaFiles=false. Unity .meta sidecars are intentionally excluded and must never be reported as missing work unless the original request explicitly asks for metadata files.");
            }

            if (RequiresFileContentRead(
                    effectiveRequest) ||
                RequiresReviewFileEvidence(
                    effectiveRequest) ||
                !string.IsNullOrWhiteSpace(
                    fileContentEvidence))
            {
                prompt.AppendLine();

                if (!string.IsNullOrWhiteSpace(
                        fileContentEvidence))
                {
                    prompt.AppendLine(
                        $"AIRuntime read '{fileContentPath}' immediately before this review. Treat the following as the authoritative file contents:");

                    prompt.AppendLine();
                    prompt.AppendLine(
                        fileContentEvidence.Trim());

                    prompt.AppendLine();
                    prompt.AppendLine(
                        "Compare the implementation summary directly with this evidence. Accurate paraphrasing is allowed. Do not invent contradictions, requirements, omissions, or alternate wording that do not appear in the authoritative contents.");

                    prompt.AppendLine(
                        "AIRuntime already performed the exact scoped read_file calls used to produce this evidence. Do not call read_file again during this review; judge the embedded authoritative contents directly.");
                }
                else
                {
                    prompt.AppendLine(
                        "AIRuntime could not resolve the requested file deterministically. You must call read_file for the requested file and compare the implementation with the returned contents. If the active request contains only a filename, call get_editor_selection to obtain its exact FolderPath, then read that file inside the folder.");
                }

                prompt.AppendLine(
                    "A directory listing, filename, acknowledgement that a summary was requested, or unsupported summary is not completion and must receive FAIL.");
            }

            if (RequiresDirectoryTree(
                    effectiveRequest))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "MANDATORY DIRECTORY TREE REVIEW: Call list_directory_tree using the exact path, depth, file-inclusion, and includeMetaFiles settings in the active request. AIRuntime will compare its raw Entries[].Path values with the implementation output. Do not infer tool contents from an earlier response.");
            }

            if (!string.IsNullOrWhiteSpace(
                    verifiedCapability))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    $"MANDATORY CAPABILITY REVIEW: Call {verifiedCapability} to obtain current evidence. This capability is available for the review run; never fail the task by claiming the tool is unavailable.");
            }

            if (string.Equals(
                    verifiedCapability,
                    "inspect_game_object",
                    StringComparison.Ordinal))
            {
                prompt.AppendLine();
                prompt.AppendLine(
                    "INSPECT GAME OBJECT REVIEW RULES: Treat the fresh inspect_game_object result as authoritative for object identity, Transform, ChildCount, components, prefab state, tag, layer, activity, static state, and missing-script counts. get_editor_selection identifies the selected object and summarizes its components, but it does not enumerate that object's children; a bare HierarchyPath such as 'Cube' cannot contradict ChildCount or prove that the object has no children.");

                prompt.AppendLine(
                    "Do not compare formatted inspect_editor_component display strings character-for-character with inspect_game_object numeric values. Rounded values such as -3.30 and full-precision values such as -3.29714155 are consistent when they agree within the displayed precision. Quaternion local rotation (0,0,0,1) and Euler rotation (0,0,0) are equivalent identity rotations, not a mismatch. Likewise, equal local/world values and local/lossy scale on an unparented object are supporting evidence, not discrepancies.");

                prompt.AppendLine(
                    "Do not invent expected transform, world transform, child, or hierarchy values that are absent from a tool result. FAIL only for an actual contradictory returned value, an omitted field required by the card, or an unsupported claim. The mere presence of a missing script is not a verification failure when inspect_game_object reports it accurately and the card only asks to inspect rather than repair it.");
            }

            if (effectiveRequest.IndexOf(
                    "[AIRUNTIME_COMPONENT_EVIDENCE_POLICY:SKIP]",
                    StringComparison.Ordinal) < 0 &&
                !IsStandaloneMaterialAssetReview(
                    true,
                    effectiveRequest) &&
                !IsStandaloneDirectoryScaffoldingTask(
                    effectiveRequest) &&
                !IsStandaloneScriptAssetReview(
                    true,
                    effectiveRequest) &&
                (RequiresComponentInspection(
                     effectiveRequest) ||
                 RequiresReviewComponentInspection(
                     effectiveRequest) ||
                 !string.IsNullOrWhiteSpace(
                     componentInspectionEvidence)))
            {
                prompt.AppendLine();

                if (!string.IsNullOrWhiteSpace(
                        componentInspectionEvidence))
                {
                    prompt.AppendLine(
                        "AIRuntime captured the following authoritative inspect_editor_component result immediately before this review:");

                    prompt.AppendLine();
                    prompt.AppendLine(
                        componentInspectionEvidence.Trim());

                    prompt.AppendLine();
                    prompt.AppendLine(
                        "Compare the implementation against these actual component settings and diagnostics. Generic troubleshooting without the concrete inspection evidence is a FAIL. Current authoritative inspection proving the required final configuration is sufficient unless the acceptance criterion explicitly requires a particular mutation call or its returned result; do not demand historical mutation output merely to prove an already-verified final state. An authoritative inspect_scene result with Dirty=false proves the scene is presently saved. A request phrased as 'fix it if possible, otherwise give options' does not require an unsafe mutation: when MeshColliderMissingMesh is present and the evidence provides no authoritative source mesh, PASS is allowed if the implementation clearly explains that limitation, does not invent a mesh reference, and gives concrete manual repair options. Do not fail solely because no mutation occurred in that case.");
                }
                else
                {
                    prompt.AppendLine(
                        "The component inspection evidence is unavailable, so the review cannot verify the diagnosis or any claimed fix. The verdict must be FAIL.");
                }
            }

            prompt.AppendLine(
                "PASS is allowed only when every requested detail is present, correct, and supported by the evidence. If you identify any missing or incorrect work, even if it seems minor, the verdict must be FAIL.");

            prompt.AppendLine();
            prompt.AppendLine(
                $"Original card title (context only when a current active request is present): {title?.Trim()}");

            prompt.AppendLine();
            prompt.AppendLine(
                "Original card description:");
            prompt.AppendLine(
                description?.Trim() ??
                string.Empty);

            prompt.AppendLine();
            prompt.AppendLine(
                "Original card acceptance criteria:");
            prompt.AppendLine(
                acceptanceCriteria?.Trim() ??
                string.Empty);

            prompt.AppendLine();
            prompt.AppendLine("Implementation agent output:");
            prompt.AppendLine(
                agentOutput?.Trim() ??
                string.Empty);


            prompt.AppendLine();
            prompt.AppendLine(
                "FAIL SCOPE CHECK: Before returning FAIL, map every claimed defect to an exact requirement in this card's local Description or AcceptanceCriteria. Do not cite a parent-wide outcome allocated to an unfinished SiblingContract, and do not turn a preference or typical practice into a requirement. If all local criteria are satisfied by current evidence, return PASS even though later sibling integration remains unfinished.");

            prompt.AppendLine();
            prompt.AppendLine(
                "Your final response must begin with exactly one verdict line:");

            prompt.AppendLine(
                "KANBAN_REVIEW: PASS");

            prompt.AppendLine(
                "or");

            prompt.AppendLine(
                "KANBAN_REVIEW: FAIL");

            prompt.AppendLine(
                "After the verdict, list the evidence checked and any missing or incorrect work. Never write PASS if the missing-or-incorrect section contains an issue.");

            return prompt.ToString();
        }


        private static string SanitizeReviewHandoffEvidenceClaims(
            string handoffMessage,
            string effectiveRequest,
            string fileContentEvidence,
            string componentInspectionEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    handoffMessage))
            {
                return handoffMessage;
            }


            string sanitized =
                handoffMessage;

            bool componentEvidenceIsRequired =
                RequiresReviewComponentInspection(
                    effectiveRequest);

            if (!componentEvidenceIsRequired ||
                !string.IsNullOrWhiteSpace(
                    componentInspectionEvidence))
            {
                sanitized =
                    Regex.Replace(
                        sanitized,
                        @"(?:The\s+)?component\s+inspection\s+evidence\s+is\s+unavailable[^.\r\n]*\.?",
                        string.Empty,
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);
            }

            if (!string.IsNullOrWhiteSpace(
                    fileContentEvidence))
            {
                sanitized =
                    Regex.Replace(
                        sanitized,
                        @"(?:No\s+)?(?:actual\s+)?read_file\s+(?:tool\s+)?result\s+(?:was\s+)?(?:supplied|provided|captured|available)[^.\r\n]*\.?",
                        string.Empty,
                        RegexOptions.IgnoreCase |
                        RegexOptions.CultureInvariant);
            }

            return Regex.Replace(
                    sanitized,
                    @"(?m)^[ \t]+$",
                    string.Empty,
                    RegexOptions.CultureInvariant)
                .Trim();
        }


        private static string EnsureReviewVerdict(
            string response,
            string liveSelectionEvidence,
            string rootFileEvidence,
            string implementationOutput,
            bool requiresFileContentRead,
            string fileContentEvidence,
            ToolResultCaptureObserver fileReadCapture,
            bool requiresDirectoryTree,
            string effectiveRequest,
            ToolResultCaptureObserver directoryTreeCapture)
        {
            string normalizedResponse =
                response?.Trim() ??
                string.Empty;

            if (requiresFileContentRead &&
                string.IsNullOrWhiteSpace(
                    fileContentEvidence) &&
                (fileReadCapture == null ||
                 fileReadCapture.CompletedCount ==
                 0))
            {
                string reviewerBody =
                    RemoveReviewVerdictLine(
                        normalizedResponse);

                return "KANBAN_REVIEW: FAIL\n\n" +
                       "AIRuntime verification: the reviewer did not complete the required read_file call, so the implementation's file-content claims were not checked against the file." +
                       (string.IsNullOrWhiteSpace(
                            reviewerBody)
                           ? string.Empty
                           : "\n\n" +
                             reviewerBody);
            }

            if (!string.IsNullOrWhiteSpace(
                    rootFileEvidence))
            {
                bool rootListMatches =
                    RootFileOutputMatchesEvidence(
                        rootFileEvidence,
                        implementationOutput);

                string reviewerBody =
                    RemoveReviewVerdictLine(
                        normalizedResponse);

                return (rootListMatches
                           ? "KANBAN_REVIEW: PASS"
                           : "KANBAN_REVIEW: FAIL") +
                       "\n\nAIRuntime verified the model's implementation output against the authoritative root-file snapshot." +
                       (string.IsNullOrWhiteSpace(
                            reviewerBody)
                           ? string.Empty
                           : "\n\n" +
                             reviewerBody);
            }


            if (requiresDirectoryTree)
            {
                if (directoryTreeCapture ==
                        null ||
                    directoryTreeCapture.CompletedCount ==
                    0 ||
                    string.IsNullOrWhiteSpace(
                        directoryTreeCapture.Result))
                {
                    return
                        "KANBAN_REVIEW: FAIL\n\n" +
                        "AIRuntime verification: the reviewer did not complete the required list_directory_tree call.";
                }

                IReadOnlyList<string> expectedPaths =
                    ParseDirectoryTreePaths(
                        directoryTreeCapture.Result);

                bool containsForbiddenMeta =
                    ExcludesMetaFiles(
                        effectiveRequest) &&
                    expectedPaths.Any(
                        path =>
                            path.EndsWith(
                                ".meta",
                                StringComparison.OrdinalIgnoreCase));

                bool outputMatches =
                    !containsForbiddenMeta &&
                    ExactPathOutputMatches(
                        expectedPaths,
                        implementationOutput);

                return
                    (outputMatches
                        ? "KANBAN_REVIEW: PASS"
                        : "KANBAN_REVIEW: FAIL") +
                    "\n\nAIRuntime compared the implementation output directly with the authoritative list_directory_tree Entries[].Path values. " +
                    (containsForbiddenMeta
                        ? "The capability returned forbidden .meta entries."
                        : outputMatches
                            ? "The paths match exactly and the current tool result contains no forbidden .meta entries."
                            : "The implementation output does not exactly match the current returned paths.");
            }

            string recognizedVerdict =
                ExtractUnambiguousReviewVerdict(
                    normalizedResponse,
                    out bool hasConflictingVerdicts);

            if (hasConflictingVerdicts)
            {
                string body =
                    RemoveReviewVerdictLine(
                        normalizedResponse);

                return "KANBAN_REVIEW: FAIL\n\n" +
                       "AIRuntime verification: the reviewer returned conflicting PASS and FAIL verdicts." +
                       (string.IsNullOrWhiteSpace(body)
                           ? string.Empty
                           : "\n\n" + body);
            }

            if (!string.IsNullOrWhiteSpace(
                    recognizedVerdict))
            {
                string body =
                    RemoveReviewVerdictLine(
                        normalizedResponse);

                return $"KANBAN_REVIEW: {recognizedVerdict}" +
                       (string.IsNullOrWhiteSpace(body)
                           ? string.Empty
                           : "\n\n" + body);
            }


            bool? selectionIsClean =
                TryGetSelectionCleanState(
                    liveSelectionEvidence);


            if (!selectionIsClean.HasValue)
            {
                return normalizedResponse;
            }


            string verdict =
                selectionIsClean.Value
                    ? "PASS"
                    : "FAIL";

            string explanation =
                selectionIsClean.Value
                    ? "AIRuntime fallback: the reviewer omitted its verdict line; the authoritative fresh selection snapshot confirms zero missing scripts on every selected GameObject."
                    : "AIRuntime fallback: the reviewer omitted its verdict line; the authoritative fresh selection snapshot confirms that at least one selected GameObject still has missing scripts.";

            return $"KANBAN_REVIEW: {verdict}\n\n" +
                   explanation +
                   (string.IsNullOrWhiteSpace(
                        normalizedResponse)
                       ? string.Empty
                       : "\n\n" +
                         normalizedResponse);
        }


        private static bool? TryGetSelectionCleanState(
            string liveSelectionEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    liveSelectionEvidence))
            {
                return null;
            }


            try
            {
                JObject root =
                    JObject.Parse(
                        liveSelectionEvidence);

                JArray objects =
                    root["Objects"] as
                        JArray ??
                    root["objects"] as
                        JArray;


                if (objects ==
                    null)
                {
                    return null;
                }


                foreach (
                    JObject selectedObject
                    in objects.OfType<JObject>())
                {
                    bool isGameObject =
                        selectedObject["IsGameObject"]?.Value<bool>() ??
                        selectedObject["isGameObject"]?.Value<bool>() ??
                        false;

                    int missingCount =
                        selectedObject["MissingComponentCount"]?.Value<int>() ??
                        selectedObject["missingComponentCount"]?.Value<int>() ??
                        0;


                    if (isGameObject &&
                        missingCount >
                        0)
                    {
                        return false;
                    }
                }


                return true;
            }
            catch (JsonException)
            {
                return null;
            }
        }


        private static bool RootFileOutputMatchesEvidence(
            string rootFileEvidence,
            string implementationOutput)
        {
            IReadOnlyList<string> expected =
                ParseRootFilePaths(
                    rootFileEvidence);

            string[] actual =
                (implementationOutput ??
                 string.Empty)
                .Split(
                    new[]
                    {
                        "\r\n",
                        "\n",
                        "\r"
                    },
                    StringSplitOptions.RemoveEmptyEntries)
                .Select(
                    line =>
                        line.Trim())
                .Where(
                    line =>
                        line.Length >
                        0)
                .OrderBy(
                    line =>
                        line,
                    StringComparer.Ordinal)
                .ToArray();

            return expected.SequenceEqual(
                actual,
                StringComparer.Ordinal);
        }

        private static string EnforceDirectoryTreeOutputContract(
            string toolResult,
            string effectiveRequest)
        {
            if (string.IsNullOrWhiteSpace(
                    toolResult))
            {
                throw new InvalidOperationException(
                    "The model did not complete the required list_directory_tree tool call.");
            }

            IReadOnlyList<string> paths =
                ParseDirectoryTreePaths(
                    toolResult);

            if (ExcludesMetaFiles(
                    effectiveRequest) &&
                paths.Any(
                    path =>
                        path.EndsWith(
                            ".meta",
                            StringComparison.OrdinalIgnoreCase)))
            {
                throw new InvalidOperationException(
                    "list_directory_tree returned a .meta path even though the request excludes metadata files.");
            }

            return string.Join(
                "\n",
                paths);
        }


        private static IReadOnlyList<string> ParseDirectoryTreePaths(
            string toolResult)
        {
            try
            {
                JObject result =
                    JObject.Parse(
                        toolResult);

                JArray entries =
                    result["Entries"] as
                        JArray ??
                    result["entries"] as
                        JArray;

                if (entries ==
                    null)
                {
                    throw new InvalidOperationException(
                        "list_directory_tree did not return an Entries collection.");
                }

                return entries
                    .OfType<JObject>()
                    .Select(
                        entry =>
                            entry["Path"]?.Value<string>() ??
                            entry["path"]?.Value<string>())
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(
                                path))
                    .Select(
                        path =>
                            path.Trim())
                    .ToArray();
            }
            catch (JsonException exception)
            {
                throw new InvalidOperationException(
                    "AIRuntime could not parse the list_directory_tree result.",
                    exception);
            }
        }


        private static bool ExcludesMetaFiles(
            string request)
        {
            if (string.IsNullOrWhiteSpace(
                    request))
            {
                return false;
            }

            return request.IndexOf(
                       "includeMetaFiles=false",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "includeMetaFiles: false",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "exclude meta",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "exclude .meta",
                       StringComparison.OrdinalIgnoreCase) >=
                   0 ||
                   request.IndexOf(
                       "no .meta",
                       StringComparison.OrdinalIgnoreCase) >=
                   0;
        }


        private static bool ExactPathOutputMatches(
            IEnumerable<string> expectedPaths,
            string output)
        {
            string[] expected =
                (expectedPaths ??
                 Enumerable.Empty<string>())
                .OrderBy(
                    path =>
                        path,
                    StringComparer.Ordinal)
                .ToArray();

            string[] actual =
                (output ??
                 string.Empty)
                .Split(
                    new[]
                    {
                        "\r\n",
                        "\n",
                        "\r"
                    },
                    StringSplitOptions.RemoveEmptyEntries)
                .Select(
                    line =>
                        line.Trim())
                .Where(
                    line =>
                        line.Length >
                        0)
                .OrderBy(
                    line =>
                        line,
                    StringComparer.Ordinal)
                .ToArray();

            return expected.SequenceEqual(
                actual,
                StringComparer.Ordinal);
        }


        private static string EnforceRootFileOutputContract(
            string modelResponse,
            string toolResult)
        {
            if (string.IsNullOrWhiteSpace(
                    toolResult))
            {
                throw new InvalidOperationException(
                    "The model did not complete the required list_directory tool call.");
            }

            if (RootFileOutputMatchesEvidence(
                    toolResult,
                    modelResponse))
            {
                return modelResponse.Trim();
            }

            // The model selected and executed the capability. This adapter
            // only enforces the task's declared representation contract when
            // the natural-language completion adds prose, changes language,
            // or omits returned paths.
            return string.Join(
                "\n",
                ParseRootFilePaths(
                    toolResult));
        }


        private static string RemoveReviewVerdictLine(
            string response)
        {
            if (string.IsNullOrWhiteSpace(
                    response))
            {
                return string.Empty;
            }

            string[] lines =
                response.Split(
                    new[]
                    {
                        "\r\n",
                        "\n",
                        "\r"
                    },
                    StringSplitOptions.None);

            return string.Join(
                    "\n",
                    lines.Where(
                        line =>
                            ParseReviewVerdictLine(
                                line) ==
                            null))
                .Trim();
        }


        private static string ExtractUnambiguousReviewVerdict(
            string response,
            out bool hasConflictingVerdicts)
        {
            hasConflictingVerdicts =
                false;

            string verdict =
                null;

            foreach (string line in (response ?? string.Empty).Split(
                         new[] { "\r\n", "\n", "\r" },
                         StringSplitOptions.RemoveEmptyEntries))
            {
                string parsed =
                    ParseReviewVerdictLine(
                        line);

                if (parsed ==
                    null)
                {
                    continue;
                }

                if (verdict !=
                        null &&
                    !string.Equals(
                        verdict,
                        parsed,
                        StringComparison.OrdinalIgnoreCase))
                {
                    hasConflictingVerdicts =
                        true;
                }

                verdict ??=
                    parsed;
            }

            return verdict;
        }


        private static string ParseReviewVerdictLine(
            string line)
        {
            string normalized =
                line?.Trim() ??
                string.Empty;

            if (normalized.IndexOf(
                    "REVIEW:",
                    StringComparison.OrdinalIgnoreCase) <
                0 ||
                normalized.IndexOf(
                    "KANB",
                    StringComparison.OrdinalIgnoreCase) <
                0)
            {
                return null;
            }

            if (normalized.EndsWith(
                    "PASS",
                    StringComparison.OrdinalIgnoreCase))
            {
                return "PASS";
            }

            return normalized.EndsWith(
                       "FAIL",
                       StringComparison.OrdinalIgnoreCase)
                ? "FAIL"
                : null;
        }


        private static IReadOnlyList<string> ParseRootFilePaths(
            string rootFileEvidence)
        {
            if (string.IsNullOrWhiteSpace(
                    rootFileEvidence))
            {
                return Array.Empty<string>();
            }

            try
            {
                JArray entries =
                    JArray.Parse(
                        rootFileEvidence);

                return entries
                    .Select(
                        entry =>
                            entry.Type ==
                            JTokenType.String
                                ? entry.Value<string>()
                                : entry["Path"]?.Value<string>() ??
                                  entry["path"]?.Value<string>())
                    .Where(
                        path =>
                            !string.IsNullOrWhiteSpace(
                                path))
                    .Select(
                        path =>
                            path.Trim())
                    .Distinct(
                        StringComparer.Ordinal)
                    .OrderBy(
                        path =>
                            path,
                        StringComparer.Ordinal)
                    .ToArray();
            }
            catch (JsonException exception)
            {
                throw new InvalidOperationException(
                    "The authoritative root-file result was malformed.",
                    exception);
            }
        }
    }
}
