From ba455343f88308758b127fff502bf6d09ae3f99b Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:20:46 +0900 Subject: [PATCH 1/9] docs: remove obsolete architecture plans and run log Remove four documents under docs/ that no longer describe anything that exists in the repository: - execute-dynamic-code-windows-handoff.md was a one-off handoff note for resuming work on branch `codex/rebuild-execute-dynamic-code`, which no longer exists. Eight of the nine source paths it tells the reader to open are gone, because the onion refactor moved the whole pipeline under Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/. It also ends with a "paste this into Windows Codex" prompt, so it was never meant to outlive that session. - native-cli-simplification-plan.md proposed consolidating the Go modules into a single `cli` module with internal/{cli,unityipc,project,...} packages. The repository instead runs cli/{common,dispatcher, project-runner,release-automation}, and ADR 0002 records the deliberate decision to keep the dispatcher/runner split. Its verification snippet also still used the pre-V3 `uloop compile --wait-for-domain-reload` spelling, which V3 replaced with `--no-wait-for-domain-reload`. - unity-cli-loop-onion-refactor-plan.md was the working record of a finished refactor, still written as a plan ("Create branch...", "Test Plan"). The resulting structure is now enforced by the asmdef dependency tests it describes, so the document adds nothing a reader cannot get from the code. - execute-dynamic-code-run-log.md listed the snippets used in a single benchmark session on 2026-04-16. Nothing else in the repository linked to any of them except the handoff note, which linked to the rebuild doc and to itself. --- .../execute-dynamic-code-windows-handoff.md | 182 ------------- .../native-cli-simplification-plan.md | 74 ------ .../unity-cli-loop-onion-refactor-plan.md | 244 ------------------ docs/execute-dynamic-code-run-log.md | 50 ---- 4 files changed, 550 deletions(-) delete mode 100644 docs/architecture/execute-dynamic-code-windows-handoff.md delete mode 100644 docs/architecture/native-cli-simplification-plan.md delete mode 100644 docs/architecture/unity-cli-loop-onion-refactor-plan.md delete mode 100644 docs/execute-dynamic-code-run-log.md diff --git a/docs/architecture/execute-dynamic-code-windows-handoff.md b/docs/architecture/execute-dynamic-code-windows-handoff.md deleted file mode 100644 index eb665ca3cd..0000000000 --- a/docs/architecture/execute-dynamic-code-windows-handoff.md +++ /dev/null @@ -1,182 +0,0 @@ -# execute-dynamic-code Windows handoff - -This note is for resuming the `execute-dynamic-code` rebuild on a Windows machine. - -## Branch - -- Work on `codex/rebuild-execute-dynamic-code` -- Do not switch back to the historical reference branch unless you need to compare behavior - -## What changed already - -The rebuild has already moved to a layered shape: - -- `Entry` - - `ExecuteDynamicCodeTool` - - `UnityCliLoopServerController` -- `UseCase` - - `ExecuteDynamicCodeUseCase` - - `PrewarmDynamicCodeUseCase` -- `Infrastructure` - - `Runtime access` - - `Invocation` - - `Compilation pipeline` - - `Planning` - - `Backend build` - - `Safety + load` - -The key design rule is: - -- Entry knows only use cases -- Use cases know only `IDynamicCodeExecutionRuntime` -- Infrastructure internals are split behind module-level facades - -## Read this first - -Start with these files in order: - -1. `docs/architecture/execute-dynamic-code-rebuild.md` -2. `Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/ExecuteDynamicCodeTool.cs` -3. `Packages/src/Editor/Application/Api/Tools/UseCases/Tools/ExecuteDynamicCodeUseCase.cs` -4. `Packages/src/Editor/Application/Execution/DynamicCodeExecutionFacade.cs` -5. `Packages/src/Editor/Infrastructure/DynamicCompilation/DynamicCodeCompiler.cs` -6. `Packages/src/Editor/Infrastructure/DynamicCompilation/CompiledAssemblyBuilder.cs` - -## Important module facades - -- Runtime access - - `IDynamicCodeExecutionRuntime` - - `IDynamicCodeExecutorPool` -- Invocation - - `ICompiledCommandInvoker` -- Compilation pipeline - - `IDynamicCompilationService` -- Planning - - `IDynamicCompilationPlanner` -- Backend build - - `ICompiledAssemblyBuilder` -- Safety + load - - `ICompiledAssemblyLoader` - -## Current Windows expectation - -Windows is the biggest remaining unknown. - -Expected behavior from the current code: - -- Shared Roslyn worker is intentionally disabled on `RuntimePlatform.WindowsEditor` -- One-shot Roslyn compile may still work if Unity ships the expected `dotnet.exe` and `DotNetSdkRoslyn` layout -- If Unity's bundled Roslyn files cannot be resolved, the build should fall back to `AssemblyBuilder` - -This means: - -- Functionality may still work on Windows -- Performance may be slower than macOS -- Fast-path failures should now be visible immediately - -## Fast-path failure visibility - -If the ideal Roslyn path is not available, the code now emits both: - -- `VibeLogger.LogError` -- `Debug.LogError` - -Look for messages related to: - -- `dynamic_code_fast_path_unavailable` -- `dynamic_code_shared_worker_fallback` -- `dynamic_code_shared_worker_failure` -- `dynamic_code_one_shot_compiler_start_failure` - -Relevant files: - -- `Packages/src/Editor/Application/Compilation/DynamicCompilationHealthMonitor.cs` -- `Packages/src/Editor/Infrastructure/DynamicCompilation/ExternalCompilerPathResolver.cs` -- `Packages/src/Editor/Infrastructure/DynamicCompilation/RoslynCompilerBackend.cs` -- `Packages/src/Editor/Infrastructure/DynamicCompilation/SharedRoslynCompilerWorkerHost.cs` - -## Windows validation checklist - -Run these in order on Windows: - -1. `uloop compile` -2. `uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.UnityCliLoop.DynamicCodeToolTests'` -3. `uloop execute-dynamic-code --code "return 1 + 2;"` -4. `uloop execute-dynamic-code --code "StringBuilder sb = new StringBuilder(); sb.Append(\"ok\"); return sb.ToString();"` -5. `uloop execute-dynamic-code --code "UnityEngine.GameObject go = new UnityEngine.GameObject(\"bench\"); UnityEngine.Object.DestroyImmediate(go); return \"ok\";"` - -Then inspect the Unity Console for: - -- compile errors -- fallback error logs -- worker failure logs - -## How to interpret results - -- If all commands succeed and no fast-path error logs appear: - - Windows one-shot Roslyn path is probably healthy -- If commands succeed but `dynamic_code_fast_path_unavailable` appears: - - Windows is running through the `AssemblyBuilder` fallback -- If commands fail and `dynamic_code_one_shot_compiler_start_failure` appears: - - Unity's bundled `dotnet.exe` or `csc.dll` assumptions are wrong on Windows -- If commands fail and `dynamic_code_shared_worker_failure` appears: - - The worker path unexpectedly activated or broke before fallback completed - -## Likely next steps on Windows - -If Windows is broken, investigate in this order: - -1. `ExternalCompilerPathResolver` - - Are `NetCoreRuntime/dotnet.exe` and `DotNetSdkRoslyn/*` really present? -2. `RoslynCompilerBackend` - - Does the one-shot `dotnet csc.dll` process launch? -3. `AssemblyBuilderFallbackCompilerBackend` - - If Roslyn fast path is unavailable, does fallback still produce a valid DLL? -4. `SharedRoslynCompilerWorkerHost` - - Do not enable this on Windows until lifecycle, pipes, and file-lock behavior are proven stable - -## Current quality bar before changing more code - -Do not optimize Windows first. First make sure: - -- basic execution works -- diagnostics are visible -- fallback behavior is understandable -- the current tests stay green - -Only after that should Windows-specific performance work start. - -## Paste this into Windows Codex - -Use this prompt as the starting message on Windows: - -```text -Please resume work on branch `codex/rebuild-execute-dynamic-code`. - -First read: -- docs/architecture/execute-dynamic-code-rebuild.md -- docs/architecture/execute-dynamic-code-windows-handoff.md - -Important recent commits: -- 583cde7e Log dynamic compile fast-path failures for Windows handoff -- 893f42f2 Route prewarm capability through build facade -- 0889d2c6 Split dynamic code infrastructure into module facades - -Goal: -- Validate execute-dynamic-code on Windows -- Confirm whether the fast Roslyn path, one-shot Roslyn path, or AssemblyBuilder fallback is being used -- If the ideal path is not active, inspect Unity Console error logs and fix the cause - -Suggested validation commands: -1. uloop compile -2. uloop run-tests --test-mode EditMode --filter-type regex --filter-value 'io.github.hatayama.UnityCliLoop.DynamicCodeToolTests' -3. uloop execute-dynamic-code --code "return 1 + 2;" -4. uloop execute-dynamic-code --code "StringBuilder builder1002 = new StringBuilder(); builder1002.Append(\"ok-1002\"); return builder1002.ToString();" -5. uloop execute-dynamic-code --code "DynamicAssemblyTest test1004 = new DynamicAssemblyTest(); return test1004.HelloWorld();" - -Watch the Unity Console for: -- dynamic_code_fast_path_unavailable -- dynamic_code_shared_worker_fallback -- dynamic_code_shared_worker_failure -- dynamic_code_one_shot_compiler_start_failure -``` diff --git a/docs/architecture/native-cli-simplification-plan.md b/docs/architecture/native-cli-simplification-plan.md deleted file mode 100644 index dbe145b916..0000000000 --- a/docs/architecture/native-cli-simplification-plan.md +++ /dev/null @@ -1,74 +0,0 @@ -# Native CLI Simplification Plan - -## 背景 - -`cli` は小さな Go CLI アプリケーションです。以前の構成は Clean Architecture の語彙に寄せて `presentation`、`application`、`ports`、`adapters` に分かれていましたが、CLI の規模に対して抽象化が強くなっていました。 - -この整理では、Clean Architecture の層名を守ることよりも、CLI として実際に何をしているかが読み取りやすい構成を優先します。ただし SOLID、デメテルの法則、凝集度は維持し、特に機能的凝集を優先します。 - -## 現状の違和感 - -旧 `internal/presentation` は、実質的には CLI の入出力層でした。ここには引数解釈、help、completion、stdout/stderr、tool list、skills、update、Unity RPC 実行の入口がまとまっていました。 - -このまとまり自体は間違いではありませんが、`presentation` という名前だけでは terminal CLI の責務が伝わりづらく、さらに `tools` や `update` など独立した機能も同じ package に混ざっていました。 - -また、`internal/application/tool_dispatcher.go` と `internal/ports/unity_bridge.go` は、抽象化のための抽象化に近い状態でした。Unity 以外の送信先があるわけではないため、最初から port/interface を置くより、実際の痛みが出てから抽象化する方がシンプルです。 - -## 目指す構成 - -ゼロから設計するなら、抽象レイヤー名ではなく機能名で分けます。 - -```text -cli -├── cmd/uloop/ -├── internal/ -│ ├── cli/ # args, help, completion, stdout/stderr, command routing -│ ├── unityipc/ # connection, framing, Unity RPC client -│ ├── project/ # project root, cache path, Unity project discovery -│ ├── skills/ # CLI-only skill sources and skill file-layout knowledge -│ ├── update/ # self update, installer URL, release tag selection -│ ├── tools/ # default-tools.json, tool list/cache DTO and loading -│ └── version/ # semver compare -├── contract.json # CLI version contract exposed by root package -└── dist/ # ignored local build and release output -``` - -この構成では、読み手は「CLI の入力処理」「Unity との IPC」「tool catalog」「update 処理」のように、実際の機能単位でコードを探せます。 - -## 段階的な進め方 - -一気に全部を動かすのではなく、小さい commit に分けて進めます。 - -1. `Core~`、`Dispatcher~`、`Shared~` に分かれていた Go modules を `cli` に統合する。 -2. `internal/presentation` を `internal/cli` に rename する。 -3. `internal/application` と `internal/ports` を削除し、`cli` から `unityipc.Client` を直接呼ぶ。 -4. `adapters/unity` と `adapters/framing` を `internal/unityipc` に寄せる。 -5. `adapters/project` を `internal/project` に寄せる。 -6. `default-tools.json` と tool catalog/cache DTO を `internal/tools` へ移す。 -7. CLI-only skill source path を `internal/skills` 配下へ移し、CLI が直接 file layout を持たないようにする。 -8. update installer URL と release tag 選択を `internal/update` に寄せる。 - -## やらないこと - -この整理では、大きな設計変更や互換性維持用の抽象化は追加しません。 - -- Unity 以外の backend を想定した interface は先に作らない。 -- HTTP/TUI など、存在しない presentation surface を想定した階層は作らない。 -- pure value transform だけの小さな処理を無理に service 化しない。 -- public command の挙動や出力形式は変えない。 -- package 間の情報の受け渡しでは、できるだけ意味のある DTO を使い、生成後に書き換えない前提で扱う。 - -## 検証方針 - -各ステップでは、少なくとも次を確認します。 - -```bash -scripts/check-go-cli.sh -dist/darwin-arm64/uloop compile --wait-for-domain-reload -``` - -skill discovery や CLI-only skill source path を動かした場合は、`ToolSkillSynchronizerTests` の targeted EditMode test も実行します。 - -```bash -dist/darwin-arm64/uloop run-tests --test-mode EditMode --filter-type regex --filter-value ToolSkillSynchronizerTests -``` diff --git a/docs/architecture/unity-cli-loop-onion-refactor-plan.md b/docs/architecture/unity-cli-loop-onion-refactor-plan.md deleted file mode 100644 index 161a7df654..0000000000 --- a/docs/architecture/unity-cli-loop-onion-refactor-plan.md +++ /dev/null @@ -1,244 +0,0 @@ -# UnityCLILoop C# Onion Architecture Refactor Plan - -## Summary - -- Work from `refactor/unity-cli-loop-onion-architecture`, created from `origin/v3-beta`. -- Treat the UnityCLILoop domain as the platform that safely hosts and executes Unity operation tools, not as the individual Unity operations themselves. -- Treat `Compile`, `GetLogs`, and `ExecuteDynamicCode` as first-party tool plugins that use the same contract, registration path, and execution path as third-party tools. -- Treat each tool implementation as an outer plugin in the Onion Architecture. -- Tool implementations depend on `UnityCLILoop.ToolContracts`, which is the inward contract defined by the platform. -- Keep the Figma diagram as the visual reference: https://www.figma.com/board/ThcM67cR9MdNRQpQgeVnvi?utm_source=codex&utm_content=edit_in_figjam&oai_id=&request_id=7ebbf16a-1ed2-4c82-9f17-9cf8201373d7 - -## Implemented Slice - -- Added `UnityCLILoop.ToolContracts` as the public tool contract assembly. -- Moved the public extension API into `ToolContracts`: - - `IUnityCliLoopTool` - - `UnityCliLoopTool` - - `UnityCliLoopToolSchema` - - `UnityCliLoopToolResponse` - - `UnityCliLoopToolAttribute` - - `UnityCliLoopSecuritySetting` - - `UnityCliLoopToolParameterValidationException` -- Renamed the main editor assembly to `UnityCLILoop.Application`. -- Added onion boundary asmdefs for the intended final shape: - - `UnityCLILoop.Domain` - - `UnityCLILoop.Presentation` - - `UnityCLILoop.Infrastructure` - - `UnityCLILoop.CompositionRoot.Editor` - - `UnityCLILoop.FirstPartyTools.Editor` -- Removed manual built-in tool registration from `UnityCliLoopToolRegistry`. -- Registered bundled tools and extension tools through the same `[UnityCliLoopTool]` attribute discovery path. -- Split `Assets/Editor/CustomCommandSamples` into `UnityCLILoop.CustomCommandSamples.Editor`, which references only `UnityCLILoop.ToolContracts`. -- Moved `get-version` out of the extension-facing tool registry and kept it as an internal bridge command for CLI readiness and diagnostics. -- Moved `get-tool-details` out of the extension-facing tool registry and kept it as an internal bridge command for CLI list/sync catalog access. -- Removed `focus-window` from the Unity-side tool registry because it is implemented as a native Go CLI command. -- Removed legacy MCP-era development tools from the runtime registry: - - `ping` - - `debug-sleep` -- Moved `control-play-mode` into `UnityCLILoop.FirstPartyTools.Editor` as the first bundled tool plugin that references only `UnityCLILoop.ToolContracts`. -- Moved `get-logs` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives Unity Console access through a `ToolContracts` host-service contract. -- Moved `clear-console` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives Unity Console mutation access through a `ToolContracts` host-service contract. -- Moved `compile` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives Unity compilation access through a `ToolContracts` host-service contract. -- Moved `execute-dynamic-code` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives dynamic-code execution access through a `ToolContracts` host-service contract. -- Moved `get-hierarchy` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives hierarchy export access through a `ToolContracts` host-service contract. -- Moved `run-tests` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives Unity Test Runner access through a `ToolContracts` host-service contract. -- Moved `find-game-objects` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives GameObject search access through a `ToolContracts` host-service contract. -- Moved `screenshot` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives Unity window and GameView capture access through a `ToolContracts` host-service contract. -- Moved `record-input` and `replay-input` into `UnityCLILoop.FirstPartyTools.Editor` as bundled tool plugins that receive input recording and replay access through `ToolContracts` host-service contracts. -- Moved `simulate-keyboard` and `simulate-mouse-input` into `UnityCLILoop.FirstPartyTools.Editor` as bundled tool plugins that receive input simulation access through `ToolContracts` host-service contracts. -- Moved `simulate-mouse-ui` into `UnityCLILoop.FirstPartyTools.Editor` as a bundled tool plugin that receives EventSystem mouse simulation access through a `ToolContracts` host-service contract. -- Moved `execute-dynamic-code` schema and response DTOs into `UnityCLILoop.ToolContracts` because both the bundled tool and the application-side execution pipeline shape those values. -- Moved concrete tool host-service wiring into `UnityCLILoop.CompositionRoot.Editor`; `UnityCliLoopToolRegistry` now asks an application-side provider for registered host services instead of constructing them directly. -- Moved concrete dynamic-code compilation service factory and runtime-service factory into `UnityCLILoop.Infrastructure`; `UnityCLILoop.CompositionRoot.Editor` still owns registration, while Infrastructure owns the concrete compiler creation. -- Moved Unity Console clear host-service implementation into `UnityCLILoop.Infrastructure`; `clear-console` receives the capability through `ToolContracts` and the composition root wires the concrete adapter. -- Moved project-root identity matching into `UnityCLILoop.Domain` as a platform safety rule; JSON-RPC request validation now delegates to the domain rule and only converts failures into tool parameter errors. -- Moved project-root canonicalization into `UnityCLILoop.Domain` so Application code can validate project identity without depending on project IPC transport types. -- Moved CLI version ordering into `UnityCLILoop.Domain` because dispatcher compatibility checks are pure platform rules with no Unity Editor, file-system, or protocol dependency. -- Moved compilation diagnostic message parsing into `UnityCLILoop.Domain` because it is a pure diagnostic normalization rule with no Unity Editor, file-system, or protocol dependency. -- Moved dynamic-code default namespace and class-name constants into `UnityCLILoop.Domain` because they are platform defaults shared by compilation and execution policies. -- Moved script-changes-while-playing policy values into `UnityCLILoop.Domain` because they are compile safety policy values interpreted by Application services. -- Moved dynamic-code compilation service ports, runtime-service ports, and registries into `UnityCLILoop.Application` because Application owns the execution flow while Infrastructure supplies concrete compiler collaborators. -- Moved dynamic-code compilation DTOs into `UnityCLILoop.Application` because they are the data contract between the application execution flow and the infrastructure compiler implementation. -- Moved concrete dynamic-code compilation collaborators into `UnityCLILoop.Infrastructure`: - - `DynamicCodeCompiler` - - `DynamicCodeSourcePreparationService` - - `DynamicCompilationPlanner` - - `CompiledAssemblyBuilder` - - `CompiledAssemblyLoadService` - - `CompiledAssemblyLoader` - - `CompilationCacheManager` - - Roslyn and AssemblyBuilder compiler backends - - source shaping, auto-using, pre-using, literal hoisting, and reference-set builders -- Removed now-unused `uLoopMCP.Editor.Shared` references from `UnityCLILoop.Infrastructure` and `UnityCLILoop.CompositionRoot.Editor`. -- Removed `uLoopMCP.Editor.Shared` as a production assembly after moving its remaining constants, logging, and domain-reload registry types into `UnityCLILoop.Application`. -- Removed stale references to the deleted shared assembly GUID from dev and editor test asmdefs. -- Added `UnityCliLoopServerApplicationFacade` so Presentation code observes and controls server lifecycle through Application instead of depending on server/controller internals. -- Removed `UnityCliLoopServerApplicationFacade` in C4-6 after Presentation received `UnityCliLoopServerApplicationService` through the bootstrap service graph. -- Added `IUnityCliLoopServerInstance` so Application use cases expose an application-owned server handle instead of returning the project IPC bridge implementation. -- Added server instance factory and lifecycle registries in `UnityCLILoop.Application`; `UnityCLILoop.CompositionRoot.Editor` registers the concrete bridge implementation. -- Moved project IPC implementation into `UnityCLILoop.Infrastructure`: - - `UnityCliLoopBridgeServer` - - `BridgeTransportEndpoint` - - `BridgeTransportListener` - - `DynamicBufferManager` - - `FrameParser` - - `MessageReassembler` -- Added registry tests proving: - - bundled tools are discovered through the attribute path. - - `get-logs` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - `compile` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - `execute-dynamic-code` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - `get-version` is not registered as an extension-facing tool. - - `get-version` still executes as an internal bridge command. - - `get-tool-details` is not registered as an extension-facing tool. - - `get-tool-details` still returns the CLI catalog through the internal bridge path. - - `focus-window` is not registered as a Unity-side tool. - - legacy MCP-era development tools are not registered. - - `hello-world` is registered as an extension tool. - - `control-play-mode` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - `control-play-mode` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `get-logs` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `clear-console` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `compile` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `execute-dynamic-code` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `get-hierarchy` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `run-tests` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `find-game-objects` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `screenshot` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `record-input` and `replay-input` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `simulate-keyboard` and `simulate-mouse-input` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `simulate-mouse-ui` skill discovery still works after moving under `UnityCLILoop.FirstPartyTools.Editor`. - - `get-hierarchy` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - hierarchy host-service contracts compile under `UnityCLILoop.ToolContracts`. - - hierarchy host-service implementation compiles under `UnityCLILoop.Application`. - - `run-tests` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - test execution host-service contracts compile under `UnityCLILoop.ToolContracts`. - - test execution host-service implementation compiles under `UnityCLILoop.Application`. - - `find-game-objects` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - GameObject search host-service contracts compile under `UnityCLILoop.ToolContracts`. - - GameObject search host-service implementation compiles under `UnityCLILoop.Application`. - - `screenshot` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - screenshot host-service contracts compile under `UnityCLILoop.ToolContracts`. - - screenshot host-service implementation compiles under `UnityCLILoop.Application`. - - `record-input` and `replay-input` are registered from `UnityCLILoop.FirstPartyTools.Editor`. - - input recording host-service contracts compile under `UnityCLILoop.ToolContracts`. - - input recording host-service implementations compile under `UnityCLILoop.Application`. - - `simulate-keyboard` and `simulate-mouse-input` are registered from `UnityCLILoop.FirstPartyTools.Editor`. - - input simulation host-service contracts compile under `UnityCLILoop.ToolContracts`. - - input simulation host-service implementations compile under `UnityCLILoop.Application`. - - `simulate-mouse-ui` is registered from `UnityCLILoop.FirstPartyTools.Editor`. - - concrete tool host services compile under `UnityCLILoop.CompositionRoot.Editor`. - - `UnityCliLoopToolRegistry` does not directly construct concrete host services. - - concrete dynamic-code compiler factory compiles under `UnityCLILoop.Infrastructure`. - - Unity Console clear host service compiles under `UnityCLILoop.Infrastructure`. - - project-root identity safety policy compiles under `UnityCLILoop.Domain`. - - CLI version ordering compiles under `UnityCLILoop.Domain`. - - compilation diagnostic message parsing compiles under `UnityCLILoop.Domain`. - - dynamic-code platform defaults compile under `UnityCLILoop.Domain`. - - script-changes-while-playing policy values compile under `UnityCLILoop.Domain`. - - dynamic-code compilation service ports, runtime-service ports, and registries compile under `UnityCLILoop.Application`. - - dynamic-code compilation DTOs compile under `UnityCLILoop.Application`. - - concrete dynamic-code compilation collaborators compile under `UnityCLILoop.Infrastructure`. - - Application source files do not reference concrete dynamic-code compilation infrastructure directly. - - `UnityCLILoop.Infrastructure` and `UnityCLILoop.CompositionRoot.Editor` no longer reference `uLoopMCP.Editor.Shared`. - - support constants, structured logging, and domain-reload registry types compile under `UnityCLILoop.Application`. - - production asmdefs no longer reference `uLoopMCP.Editor.Shared`. - - project asmdefs no longer reference the deleted shared assembly GUID. - - the sample extension asmdef references only `UnityCLILoop.ToolContracts`. - - the sample `hello-world` extension executes through the same typed contract path as bundled tools. - - `UnityCLILoop.FirstPartyTools.Editor` references only `UnityCLILoop.ToolContracts`. - - settings/setup UI code no longer reaches CLI setup internals directly and receives `CliSetupApplicationService` through editor startup. - - settings/setup UI code no longer reaches skill setup internals directly and goes through `SkillSetupApplicationFacade`. - - settings-window UI code no longer reaches tool settings or registry internals directly and goes through `ToolSettingsApplicationFacade`. - - settings-window UI source uses `UnityCliLoopSettingsWindow` naming instead of the legacy MCP settings-window name. - - settings, setup, and server editor UI files now compile under `UnityCLILoop.Presentation`. - - recordings editor UI now compiles under `UnityCLILoop.Presentation` and reaches record/replay services through `RecordingsApplicationFacade`. - - unused legacy MCP communication-log code and its transient settings have been removed. - - shared editor UI constants now use `UnityCliLoopUIConstants` naming instead of the legacy MCP name. - - editor settings storage now uses `UnityCliLoopEditorSettings` naming instead of the legacy MCP name. - - editor domain reload state provider now uses `UnityCliLoopEditorDomainReloadStateProvider` naming instead of the legacy MCP name. - - project path resolution now uses `UnityCliLoopPathResolver` naming instead of the legacy MCP name. - - package version, tool availability, console log filter, shared constants, and server config now use `UnityCliLoop*` naming instead of legacy MCP names. - - project IPC server lifecycle types now use `UnityCliLoop*` naming instead of legacy MCP names. - - presentation USS, UXML, and C# style class names now use `unity-cli-loop-*` prefixes instead of the legacy `mcp-*` prefix. - - public tool source files now live under `Packages/src/Editor/FirstPartyTools` instead of the legacy `Packages/src/Editor/Api/McpTools` folder. - - pure platform values `ToolDisabledException`, `ValidationResult`, and `ServiceResult` now compile under `UnityCLILoop.Domain`. - - dynamic-code compiler factory registration now compiles under `UnityCLILoop.CompositionRoot.Editor`. - - server lifecycle facade, server handle, server factory registry, and server lifecycle registry compile under `UnityCLILoop.Application`. - - project IPC transport implementation compiles under `UnityCLILoop.Infrastructure`. - - Application source files do not reference project IPC implementation classes directly. -- Added asmdef dependency tests proving: - - `Domain` and `ToolContracts` have no project assembly references. - - `Application` references the inward contracts and does not reference outer onion layers. - - `Presentation` and `Infrastructure` are sibling outer layers and do not reference each other. - - `CompositionRoot.Editor` is the assembly allowed to reference all onion assemblies. - -## Key Changes - -- Create `refactor/unity-cli-loop-onion-architecture` from `origin/v3-beta`. -- Restructure asmdef boundaries around: - - `UnityCLILoop.Domain` - - `UnityCLILoop.ToolContracts` - - `UnityCLILoop.Application` - - `UnityCLILoop.Presentation` - - `UnityCLILoop.Infrastructure` - - `UnityCLILoop.CompositionRoot.Editor` - - `UnityCLILoop.FirstPartyTools.Editor` -- Keep `Domain` focused on platform rules. -- Keep `Application` focused on tool hosting, registration, catalog, and execution policy. -- Keep `Presentation` and `Infrastructure` as sibling outer layers that do not reference each other. -- Keep `CompositionRoot.Editor` as the only assembly that references all layers and first-party tools for DI wiring and registration. -- Keep `FirstPartyTools.Editor` as an outer plugin assembly that references only `ToolContracts`. -- Move editor/runtime session state out of static facades and into instance services where the state has a lifecycle: - - CLI setup and skill installation detection - - tool, ULoop, and editor settings repositories - - tool registrar and host-service provider registries - - dynamic-code server-scoped services and startup telemetry - - server controller, server factory, and lifecycle registries - - editor delay scheduling and main-thread continuation scheduling - - input recording, replay, simulation, overlay, and VibeLogger state -- Keep static entrypoints only where Unity callbacks, current public API, or generated skill compatibility still require a static surface; those entrypoints delegate to instance services. - -## Follow-up Opportunities - -- Move additional cohesive platform rules into `UnityCLILoop.Domain` when they have no Unity or file-system dependency. -- Move additional Unity Editor and file-system adapters into `UnityCLILoop.Infrastructure` when Application facades or ports are available. -- Split additional internal bridge commands from public tool registration when more CLI-only commands are identified. -- Move additional startup and DI wiring into `UnityCLILoop.CompositionRoot.Editor` as concrete service composition points are identified. -- Extend asmdef dependency tests after each future physical move, so dependency direction stays enforced by Unity assemblies instead of documentation alone. - -## Public Contracts - -- Replace old public SDK names with UnityCLILoop names: - - `[McpTool]` to `[UnityCliLoopTool]` - - `McpToolAttribute` to `UnityCliLoopToolAttribute` - - `IUnityTool` to `IUnityCliLoopTool` - - `AbstractUnityTool` to `UnityCliLoopTool` - - `BaseToolSchema` to `UnityCliLoopToolSchema` - - `BaseToolResponse` to `UnityCliLoopToolResponse` - - `CustomToolManager` to `UnityCliLoopToolRegistrar` -- Remove `MCP` naming from public SDK and contract types. -- Keep MCP naming only inside infrastructure protocol implementation if it is still needed there. - -## Test Plan - -- Add or update asmdef dependency tests to verify: - - `Domain` does not reference project asmdefs. - - `ToolContracts` does not reference implementation asmdefs. - - `Application` references only `Domain` and `ToolContracts`. - - `Presentation` and `Infrastructure` do not reference each other. - - `FirstPartyTools.Editor` references only `ToolContracts`. - - `CompositionRoot.Editor` is the only assembly that can reference all layers and first-party tools. -- Add contract tests for `[UnityCliLoopTool]` and `UnityCliLoopTool` registration and execution. -- Add first-party parity coverage showing `GetLogs` and a Hello World style tool use the same catalog and execution path. -- Add source-level guard coverage so migrated static facades do not regain direct mutable static fields. -- Run `uloop compile` after C# changes and confirm there are no errors or warnings. -- Run focused EditMode and PlayMode suites around onion boundaries, static facade state, server startup protection, input replay, and input simulation. - -## Assumptions - -- Full source compatibility for existing third-party tools is not required. -- Tool implementations are external plugins from the perspective of the core Onion. -- Tool implementations should reference only `UnityCLILoop.ToolContracts`. -- First-party tools are grouped in `UnityCLILoop.FirstPartyTools.Editor` for now rather than split into per-tool asmdefs. diff --git a/docs/execute-dynamic-code-run-log.md b/docs/execute-dynamic-code-run-log.md deleted file mode 100644 index e3eea4b7b5..0000000000 --- a/docs/execute-dynamic-code-run-log.md +++ /dev/null @@ -1,50 +0,0 @@ -# execute-dynamic-code Run Log - -2026-04-16 にターミナルから実行した `uloop execute-dynamic-code` のコード一覧。 - -## Single Snippet Benchmark - -最初の計測では、次のコードを 5 回連続で実行した。 - -```csharp -using UnityEngine; return Mathf.PI; -``` - -## Distinct Snippet Benchmark - -次の 5 本のコードを 1 回ずつ実行する計測を行い、その後まったく同じ 5 本で再計測した。 - -### 1. PI - -```csharp -using UnityEngine; return Mathf.PI; -``` - -### 2. Vector Magnitude - -```csharp -using UnityEngine; Vector3 v = new Vector3(3f, 4f, 0f); return v.magnitude; -``` - -### 3. Unity Version - -```csharp -using UnityEngine; return Application.unityVersion; -``` - -### 4. Play Mode State - -```csharp -using UnityEditor; return EditorApplication.isPlaying; -``` - -### 5. Active Scene Name - -```csharp -using UnityEditor.SceneManagement; return EditorSceneManager.GetActiveScene().name; -``` - -## Notes - -- `Distinct Snippet Benchmark` の 5 本は 2 回連続で同じコードを使用した。 -- `Unity Version` の戻り値は計測タイミングによって `6000.2.10f1` と `6000.3.13f1` の 2 パターンを確認した。 From 15103750b81a153d592c310edf7667af9dbc84b0 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:21:07 +0900 Subject: [PATCH 2/9] docs: correct IPC transport in glossary The Server and CLI entries described the Unity-side endpoint as a TCP IPC endpoint. There is no TCP listener in the package: BridgeTransportEndpoint resolves to BridgeTransportKind.UnixDomainSocket on macOS/Linux and BridgeTransportKind.WindowsNamedPipe on Windows, which is also what the repository guidelines state. Since the glossary is the reference other docs and reviews are supposed to follow, a wrong transport here propagates into anything written against it. --- docs/glossary.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/glossary.md b/docs/glossary.md index 52b6a89e88..e66b9264a8 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -44,15 +44,17 @@ internal bridge command. ### Server -The TCP IPC endpoint hosted inside the Unity Editor by the package -(`UnityCliLoopBridgeServer`). It accepts short-lived sessions from the CLI, dispatches -JSON-RPC requests to tools and internal bridge commands, and reports editor readiness. +The local IPC endpoint hosted inside the Unity Editor by the package +(`UnityCliLoopBridgeServer`) — a Unix domain socket on macOS/Linux, a named pipe on +Windows, never TCP (`BridgeTransportEndpoint`). It accepts short-lived sessions from the +CLI, dispatches JSON-RPC requests to tools and internal bridge commands, and reports +editor readiness. ### CLI The `uloop` command-line interface as a whole — the user-facing entry point that talks to -the server over TCP. Physically it consists of two Go binaries: the dispatcher and the -project runner. +the server over local IPC. Physically it consists of two Go binaries: the dispatcher and +the project runner. ### Dispatcher From 173d7bc109ed09f6a9efd7e923b3210bef1460fe Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:25:45 +0900 Subject: [PATCH 3/9] docs: fix stale command and parameter references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two documented commands could not work as written. github-actions-security.md told the reader to run the workflow-pinning tests "from `cli`". `cli` is only a go.work root with no test packages; the tests live in cli/release-automation/internal/architecture. Ran the command from the corrected directory to confirm it passes. execute-dynamic-code-examples.md passed `--parameters '[6,7]'` and read the values back as `parameters[0]`. ExecuteDynamicCodeSchema.Parameters is a Dictionary, so the flag takes a JSON object and the values arrive under `param0`/`param1` — the array form never bound. The values also arrive boxed, so a direct `(int)` cast is not enough. The replacement example was run against a live Editor and returns 42. Added the "advanced, usually unnecessary" note the tool's skill file already carries, so the example does not read as the default way to pass data into a snippet. --- docs/execute-dynamic-code-examples.md | 6 ++++-- docs/github-actions-security.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/execute-dynamic-code-examples.md b/docs/execute-dynamic-code-examples.md index 5843481230..e79062c5de 100644 --- a/docs/execute-dynamic-code-examples.md +++ b/docs/execute-dynamic-code-examples.md @@ -50,12 +50,14 @@ GameObject を作って名前を返す。 uloop execute-dynamic-code --code 'using UnityEngine; GameObject go = new GameObject("SampleCube"); go.AddComponent(); return go.name;' ``` -パラメータ付きで掛け算する。 +パラメータ付きで掛け算する。`--parameters` には JSON オブジェクトを渡し、コード側からは `parameters["param0"]`, `parameters["param1"]` の順で参照する。値は `object` として届くので数値は `System.Convert` を挟む。 ```sh -uloop execute-dynamic-code --code 'int a = (int)parameters[0]; int b = (int)parameters[1]; return a * b;' --parameters '[6,7]' +uloop execute-dynamic-code --code 'int a = System.Convert.ToInt32(parameters["param0"]); int b = System.Convert.ToInt32(parameters["param1"]); return a * b;' --parameters '{"param0":6,"param1":7}' ``` +`--parameters` は上級者向けで、ほとんどのスニペットでは不要。同じスニペットをデータだけ変えて使い回すとき以外は省略する。 + ## Medium Examples Camera 情報を列挙する。 diff --git a/docs/github-actions-security.md b/docs/github-actions-security.md index 20d2122996..a73d18d3b5 100644 --- a/docs/github-actions-security.md +++ b/docs/github-actions-security.md @@ -8,7 +8,7 @@ When updating a pinned action: 1. Resolve the intended upstream tag to a commit SHA. 2. Replace the workflow `uses:` ref with the full 40-character SHA. -3. Run `go test ./internal/architecture -run 'TestWorkflowActions|TestPullRequestWorkflow' -count=1` from `cli`. +3. Run `go test ./internal/architecture -run 'TestWorkflowActions|TestPullRequestWorkflow' -count=1` from `cli/release-automation`. For nested action paths such as `github/codeql-action/upload-sarif`, resolve the tag against the action repository, not the nested action path. From ca7ade885682c7cc07b10ecfae7fcee111b562bb Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:28:27 +0900 Subject: [PATCH 4/9] docs: align execute-dynamic-code rebuild doc with current warmup path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The document still described a prewarm use case that no longer exists. `IPrewarmDynamicCodeUseCase` / `PrewarmDynamicCodeUseCase` are gone, and with them the `UnityCliLoopServerController -> PrewarmUseCase` entry edge: server readiness is now `UnityCliLoopFirstPartyServerLifecycleBinding` in the composition root, which warms the project IPC transport with the internal `get-version` command and never enters this pipeline. Dynamic-code warm-up moved into the pipeline itself, split across `DynamicCodeForegroundWarmupRunner`, `DynamicCodeForegroundWarmupState`, `ExecuteDynamicCodeReadinessProbe`, and `DynamicCodeForegroundWarmupSnippets`, with `ExecuteDynamicCodeUseCase` running the foreground fallback. Added a Warm-up module so the reason those four types share one snippet list — no path may report warm while a shape the user hits first is still cold — is visible in the diagram rather than only in the code comments. Also corrected two edges that no longer exist. `DynamicCodeExecutionFacade` does not touch `ICompiledAssemblyBuilder`; it depends on the executor pool and `DynamicCodeExecutionScheduler`, which is what arbitrates foreground versus idle-only execution. And the composition graph attributed the whole compiler collaborator set to the registry, when `DynamicCodeServicesRegistry` only wires runtime access — the planning, backend build, and safety/load collaborators are built by `DynamicCodeCompiler`'s default constructor, several hops away. Named the assembly and folder the pipeline lives in at the top, so the Entry/UseCase/Infrastructure headings are not misread as onion assemblies. --- .../execute-dynamic-code-rebuild.md | 90 ++++++++++++------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/docs/architecture/execute-dynamic-code-rebuild.md b/docs/architecture/execute-dynamic-code-rebuild.md index 388d4cc6ce..2bd7d0867d 100644 --- a/docs/architecture/execute-dynamic-code-rebuild.md +++ b/docs/architecture/execute-dynamic-code-rebuild.md @@ -2,29 +2,39 @@ This document describes the rebuilt `execute-dynamic-code` pipeline after the layering refactor. +The whole pipeline lives under `Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/` in the +`UnityCLILoop.FirstPartyTools.Editor` assembly. The `Entry` / `UseCase` / `Infrastructure` layers +below are the layering inside that tool plugin, not the onion assemblies around it. + ## Layered overview ```mermaid flowchart TD subgraph Entry["Entry layer"] Tool["ExecuteDynamicCodeTool"] - Server["UnityCliLoopServerController"] end subgraph UseCase["UseCase layer"] ExecuteUseCase["IExecuteDynamicCodeUseCase / ExecuteDynamicCodeUseCase"] - PrewarmUseCase["IPrewarmDynamicCodeUseCase / PrewarmDynamicCodeUseCase"] end subgraph Infrastructure["Infrastructure layer"] subgraph RuntimeAccess["Runtime access module"] RuntimeContract["IDynamicCodeExecutionRuntime"] RuntimeFacade["DynamicCodeExecutionFacade"] + Scheduler["DynamicCodeExecutionScheduler"] ExecutorPoolContract["IDynamicCodeExecutorPool"] ExecutorPool["DynamicCodeExecutorPool"] Provider["RegistryDynamicCodeExecutorFactory"] end + subgraph Warmup["Warm-up module"] + WarmupRunner["DynamicCodeForegroundWarmupRunner"] + WarmupState["DynamicCodeForegroundWarmupState"] + Probe["ExecuteDynamicCodeReadinessProbe"] + Snippets["DynamicCodeForegroundWarmupSnippets"] + end + subgraph Invocation["Invocation module"] ExecutorContract["IDynamicCodeExecutor"] Executor["DynamicCodeExecutor"] @@ -71,14 +81,17 @@ flowchart TD end Tool --> ExecuteUseCase - Server --> PrewarmUseCase ExecuteUseCase --> RuntimeContract - PrewarmUseCase --> RuntimeContract + ExecuteUseCase --> WarmupRunner + ExecuteUseCase --> WarmupState + WarmupRunner --> RuntimeContract + WarmupRunner --> Probe + Probe --> Snippets RuntimeContract --> RuntimeFacade RuntimeFacade --> ExecutorPoolContract - RuntimeFacade --> BuilderContract + RuntimeFacade --> Scheduler ExecutorPoolContract --> ExecutorPool ExecutorPool --> Provider Provider --> ExecutorContract @@ -115,57 +128,61 @@ flowchart TD ```mermaid flowchart TD - Services["DynamicCodeServices"] + Static["DynamicCodeServices (static facade)"] + Services["DynamicCodeServicesRegistry"] SourcePrepSvc["DynamicCodeSourcePreparationService"] - PathSvc["ExternalCompilerPathResolutionService"] - Planner["IDynamicCompilationPlanner / DynamicCompilationPlanner"] - Builder["ICompiledAssemblyBuilder / CompiledAssemblyBuilder"] - LoadSvc["ICompiledAssemblyLoader / CompiledAssemblyLoadService"] - Backend["DynamicCompilationBackend"] EntryResolver["CompiledCommandEntryPointResolver"] + CompilerFactory["DynamicCodeCompilationServiceFactory"] Provider["RegistryDynamicCodeExecutorFactory"] ExecutorPool["IDynamicCodeExecutorPool / DynamicCodeExecutorPool"] RuntimeFacade["DynamicCodeExecutionFacade"] + Scheduler["DynamicCodeExecutionScheduler"] ExecuteUseCase["ExecuteDynamicCodeUseCase"] - PrewarmUseCase["PrewarmDynamicCodeUseCase"] + Static --> Services Services --> SourcePrepSvc - Services --> PathSvc - Services --> Planner - Services --> Builder - Services --> LoadSvc - Services --> Backend Services --> EntryResolver Services --> Provider Services --> ExecutorPool Services --> RuntimeFacade Services --> ExecuteUseCase - Services --> PrewarmUseCase + Provider --> CompilerFactory Provider --> SourcePrepSvc Provider --> EntryResolver ExecutorPool --> Provider RuntimeFacade --> ExecutorPool - RuntimeFacade --> Builder + RuntimeFacade --> Scheduler ExecuteUseCase --> RuntimeFacade - PrewarmUseCase --> RuntimeFacade ``` +The registry only knows the runtime-access collaborators above. `DynamicCodeCompilationServiceFactory` +just returns a `DynamicCodeCompiler`, whose default constructor is what builds the planning, +backend build, and safety/load collaborators (`DynamicCompilationPlanner`, +`CompiledAssemblyBuilder` with its path/reference/backend services, and +`CompiledAssemblyLoadService`). None of those are visible to the registry. + ## Reading guide 1. Start with `Entry layer`. - `ExecuteDynamicCodeTool` only delegates the tool workflow. - - `UnityCliLoopServerController` only requests warm-up after server start and recovery. 2. Move to `UseCase layer`. - - `ExecuteDynamicCodeUseCase` owns the user-facing workflow for execute-dynamic-code. - - `PrewarmDynamicCodeUseCase` owns the warm-up workflow. + - `ExecuteDynamicCodeUseCase` owns the user-facing workflow for execute-dynamic-code, including the + foreground warm-up fallback that protects the first real execution after startup or reload. 3. Only then read `Infrastructure layer`. - `Runtime access module` is the only runtime-facing gateway. + - `Warm-up module` keeps every warm-up entrypoint on the same snippets and request shape. - `Planning`, `Backend build`, `Safety + load`, and `Invocation` split the heavy mechanics into named modules. 4. Read `Composition graph` last. - - `DynamicCodeServices` is the only place that is expected to know many concrete classes at once. + - `DynamicCodeServicesRegistry` is the only place that is expected to know many concrete classes at once. - If a concrete-to-concrete edge only appears there, it is a wiring edge rather than a runtime dependency. +Server startup and recovery do not enter this pipeline. `UnityCliLoopFirstPartyServerLifecycleBinding` +in `UnityCLILoop.CompositionRoot.Editor` is the readiness probe: it resets server-scoped services and +warms the project IPC transport with the internal `get-version` bridge command, so a user-disabled tool +cannot block startup. Dynamic-code warm-up is owned entirely by this pipeline — the use case's +foreground fallback, and the background prewarm inside `DynamicCodeExecutionScheduler`. + ## Layer responsibilities - `Entry layer` @@ -188,7 +205,13 @@ flowchart TD - `Runtime access module` - Exposes `IDynamicCodeExecutionRuntime` to the use-case layer. - Reuses a single executor through `IDynamicCodeExecutorPool`. - - Queries warm-up capability through the build facade instead of reaching into path resolution directly. + - Arbitrates foreground and idle-only execution through `DynamicCodeExecutionScheduler`. + +- `Warm-up module` + - Owns the snippet shapes every warm-up path compiles. + - Keeps foreground fallback and background prewarm on the same sequence, so whichever runs first + marks the same execution shape as ready. + - Holds the "already warmed" state outside the use case. - `Planning module` - Turns `CompilationRequest` into `DynamicCompilationPlan`. @@ -219,20 +242,25 @@ flowchart TD - `ExecuteDynamicCodeUseCase` - Converts parameters into the runtime request. - Performs the missing-`return` retry. + - Runs the foreground warm-up sequence before the first real foreground execution, and skips it for + compile-only and yield-to-foreground requests. - Shapes `ExecutionResult` into `ExecuteDynamicCodeResponse`. -- `PrewarmDynamicCodeUseCase` - - Owns the single-flight warm-up flow. - - Reuses the same runtime contract as the real execution path. - - `IDynamicCodeExecutionRuntime` - Contract between use cases and runtime infrastructure. + - Exposes `ExecuteAsync` for foreground work and `TryExecuteIfIdleAsync` for work that must not + displace a foreground request. - Keeps use cases from depending on factory and executor wiring directly. - `DynamicCodeExecutionFacade` - Reuses executors through `IDynamicCodeExecutorPool`. - - Checks warm-up capability through `ICompiledAssemblyBuilder`. - - Hides provider, pool, and build-capability wiring from use cases. + - Routes both entrypoints through `DynamicCodeExecutionScheduler` so foreground work can preempt + background work. + - Hides provider, pool, and scheduling wiring from use cases. + +- `DynamicCodeForegroundWarmupRunner` + - Compiles the shared warm-up snippets in sequence, either as foreground work or as idle-only work. + - Exists so no warm-up path can report warm while a shape the user hits first is still cold. - `DynamicCodeExecutorPool` - Owns executor reuse and disposal for the runtime access path. From 1f3d138a5058148a97451a0911155b42e9625471 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:45:48 +0900 Subject: [PATCH 5/9] docs: remove generic execute-dynamic-code cookbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Of the 17 examples in this file, only the --parameters one carried information specific to uloop. The rest were plain Unity API usage — GameObject.Find, SceneManager.GetActiveScene, FindObjectsByType — which the tool's skill file explicitly tells the agent to write from its own Unity knowledge instead of copying from a catalogue. The repository already removed generic cookbooks from skill files for the same reason. The "Long Examples" section made the file actively misleading. Its whole premise was wrapping long snippets in CODE=$(cat <<'EOF' ... EOF), which --code-file replaced; the skill file now routes shell-quoting trouble to --code-file directly. This document never mentioned that flag, so its one genuinely uloop-specific technique was the superseded one. --- docs/execute-dynamic-code-examples.md | 196 -------------------------- 1 file changed, 196 deletions(-) delete mode 100644 docs/execute-dynamic-code-examples.md diff --git a/docs/execute-dynamic-code-examples.md b/docs/execute-dynamic-code-examples.md deleted file mode 100644 index e79062c5de..0000000000 --- a/docs/execute-dynamic-code-examples.md +++ /dev/null @@ -1,196 +0,0 @@ -# execute-dynamic-code Examples - -`uloop execute-dynamic-code` をターミナルから試すためのサンプル集。 - -## Quick Start - -まずは短いコードで疎通を確認する。 - -```sh -uloop execute-dynamic-code --code 'return "hello";' -``` - -実行速度を体感したい時は `time` を付けて複数回打つ。 - -```sh -time uloop execute-dynamic-code --code 'return "first";' -time uloop execute-dynamic-code --code 'return "second";' -time uloop execute-dynamic-code --code 'return "third";' -``` - -## Short Examples - -単純な計算。 - -```sh -uloop execute-dynamic-code --code 'return 1 + 2 + 3;' -``` - -配列を組み立てて返す。 - -```sh -uloop execute-dynamic-code --code 'string[] names = { "A", "B", "C" }; return string.Join(", ", names);' -``` - -Unity バージョンを返す。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine; return Application.unityVersion;' -``` - -現在の Scene 名を返す。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine.SceneManagement; return SceneManager.GetActiveScene().name;' -``` - -GameObject を作って名前を返す。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine; GameObject go = new GameObject("SampleCube"); go.AddComponent(); return go.name;' -``` - -パラメータ付きで掛け算する。`--parameters` には JSON オブジェクトを渡し、コード側からは `parameters["param0"]`, `parameters["param1"]` の順で参照する。値は `object` として届くので数値は `System.Convert` を挟む。 - -```sh -uloop execute-dynamic-code --code 'int a = System.Convert.ToInt32(parameters["param0"]); int b = System.Convert.ToInt32(parameters["param1"]); return a * b;' --parameters '{"param0":6,"param1":7}' -``` - -`--parameters` は上級者向けで、ほとんどのスニペットでは不要。同じスニペットをデータだけ変えて使い回すとき以外は省略する。 - -## Medium Examples - -Camera 情報を列挙する。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine; Camera[] cameras = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach (Camera cam in cameras) { sb.AppendLine(cam.name + " depth=" + cam.depth); } return sb.ToString();' -``` - -Renderer の数を集計する。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine; Renderer[] renderers = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None); int enabledCount = 0; foreach (Renderer r in renderers) { if (r.enabled) { enabledCount++; } } return "renderers=" + renderers.Length + ", enabled=" + enabledCount;' -``` - -ルート GameObject 名を並べる。 - -```sh -uloop execute-dynamic-code --code 'using UnityEngine; using UnityEngine.SceneManagement; GameObject[] roots = SceneManager.GetActiveScene().GetRootGameObjects(); System.Collections.Generic.List names = new System.Collections.Generic.List(); foreach (GameObject root in roots) { names.Add(root.name); } return string.Join(", ", names);' -``` - -## Long Examples - -長いコードは変数に入れてから渡すと扱いやすい。 - -Scene 全体をざっと調べる例。 - -```sh -CODE=$(cat <<'EOF' -using UnityEngine; -using UnityEngine.SceneManagement; -using System.Text; - -Scene scene = SceneManager.GetActiveScene(); -GameObject[] roots = scene.GetRootGameObjects(); -StringBuilder sb = new StringBuilder(); - -sb.AppendLine("Scene: " + scene.name); -sb.AppendLine("Root count: " + roots.Length); - -foreach (GameObject root in roots) -{ - sb.AppendLine("- " + root.name); - - Transform[] children = root.GetComponentsInChildren(true); - sb.AppendLine(" transforms=" + children.Length); - - Renderer[] renderers = root.GetComponentsInChildren(true); - sb.AppendLine(" renderers=" + renderers.Length); -} - -return sb.ToString(); -EOF -) -uloop execute-dynamic-code --code "$CODE" -``` - -特定オブジェクトを探して移動する例。 - -```sh -CODE=$(cat <<'EOF' -using UnityEngine; - -GameObject target = GameObject.Find("Main Camera"); -if (target == null) -{ - return "Main Camera not found"; -} - -Vector3 before = target.transform.position; -target.transform.position = before + new Vector3(0f, 1f, 0f); -Vector3 after = target.transform.position; - -return "moved: " + before + " -> " + after; -EOF -) -uloop execute-dynamic-code --code "$CODE" -``` - -Hierarchy をフラットに列挙する例。 - -```sh -CODE=$(cat <<'EOF' -using UnityEngine; -using UnityEngine.SceneManagement; -using System.Collections.Generic; - -List lines = new List(); -GameObject[] roots = SceneManager.GetActiveScene().GetRootGameObjects(); - -foreach (GameObject root in roots) -{ - Transform[] all = root.GetComponentsInChildren(true); - foreach (Transform t in all) - { - lines.Add(t.name + " | activeSelf=" + t.gameObject.activeSelf); - } -} - -return string.Join("\n", lines); -EOF -) -uloop execute-dynamic-code --code "$CODE" -``` - -コンポーネントを追加して設定する例。 - -```sh -CODE=$(cat <<'EOF' -using UnityEngine; - -GameObject host = GameObject.Find("DynamicHost"); -if (host == null) -{ - host = new GameObject("DynamicHost"); -} - -Rigidbody rb = host.GetComponent(); -if (rb == null) -{ - rb = host.AddComponent(); -} - -rb.useGravity = false; -rb.mass = 2.5f; - -return host.name + " mass=" + rb.mass + " useGravity=" + rb.useGravity; -EOF -) -uloop execute-dynamic-code --code "$CODE" -``` - -## Tips - -- `launch -r` 直後は `prewarm` がまだ終わっていないことがある -- 速度比較をしたい時は、同じコマンドを `time` 付きで 2 回以上打つ -- 長いコードは `CODE=$(cat <<'EOF' ... EOF)` 形式が扱いやすい From 2500251d720a7ff2c7899ba97c94f87e63fa054d Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:46:42 +0900 Subject: [PATCH 6/9] docs: correct the documented complexity threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Operating Policy still described the original rollout at a maximum cyclomatic complexity of 25. The threshold has since been lowered to 15 in both places that declare it — MAX_COMPLEXITY in scripts/check-code-complexity.sh and cyclop.max-complexity in cli/.golangci-complexity.yml — and the workflow's artifact step passes 15 as well. Running the script locally prints "max 15" for both stacks. The closing advice was inverted by the same drift: it told the reader not to lower the repository-wide threshold yet, when it had already been lowered. Replaced it with the guidance that actually matters for a check that never fails the build — the report only helps if someone reads it. Also named the two declaration sites and the workflow's trigger paths, so the next threshold change has an obvious checklist and this document is less likely to drift again. --- docs/code-complexity.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/code-complexity.md b/docs/code-complexity.md index 40e6ba36cc..80b66b816c 100644 --- a/docs/code-complexity.md +++ b/docs/code-complexity.md @@ -30,13 +30,18 @@ CODE_COMPLEXITY_FAIL_ON_EXCEEDED=true scripts/check-code-complexity.sh Run the C# checker directly: ```sh -dotnet run --project tools/UnityCliLoop.CodeComplexity -- --root . --max-complexity 25 +dotnet run --project tools/UnityCliLoop.CodeComplexity -- --root . --max-complexity 15 ``` ## Operating Policy -Start with a maximum cyclomatic complexity of 25. This matches the default threshold for CA1502 and avoids turning existing hotspots into immediate PR blockers. +The repository-wide maximum cyclomatic complexity is 15. It is declared in two places that must +stay in step: `MAX_COMPLEXITY` in `scripts/check-code-complexity.sh` and `cyclop.max-complexity` +in `cli/.golangci-complexity.yml`. The `Code Complexity` workflow runs on every pull request that +touches `cli/**/*.go` or `Packages/src/**/*.cs`, reports findings, and uploads per-module JSON +artifacts — it never fails the build. When touching a reported function, prefer reducing complexity before adding behavior. Use tests first, then refactor with small steps such as guard clauses, Extract Method, or data-driven dispatch. -Do not lower the repository-wide threshold until the current warning list is small enough that the stricter threshold creates actionable review feedback. +Because the check is advisory, the threshold only does its job if someone reads the report. Treat +a new finding in code you are already editing as review feedback, not as noise to defer. From cbd0afdc0fcba956fc4cef3f6307eac772b31a15 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 15:47:23 +0900 Subject: [PATCH 7/9] docs: reduce execute-dynamic-code notes to what code cannot state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Of the previous 314 lines, 144 were mermaid structure diagrams and 70 were a one-line-per-class list. Both duplicate what the source already declares, and both are what actually rotted: every error found in the preceding commit lived in the diagrams — a use case that no longer exists, a facade edge to ICompiledAssemblyBuilder that was never there, five dependencies attributed to a registry that does not hold them. The duplication is not worth carrying. The 101 files in this folder hold 263 summary and Why comments between them, and the repository's comment policy requires the rationale to live there. So the diagrams competed with the code for the same job and lost. What is left is the part reading the code does not give you: the order to read it in, the rule each module boundary was chosen to satisfy, and the cross-cutting design intent. That content does not change when a class moves, so this file should now stay true across refactors instead of needing a sweep after each one. Renamed the heading away from "rebuild", which described a migration that finished rather than the document's subject. --- .../execute-dynamic-code-rebuild.md | 351 +++--------------- 1 file changed, 44 insertions(+), 307 deletions(-) diff --git a/docs/architecture/execute-dynamic-code-rebuild.md b/docs/architecture/execute-dynamic-code-rebuild.md index 2bd7d0867d..2f5022dc1f 100644 --- a/docs/architecture/execute-dynamic-code-rebuild.md +++ b/docs/architecture/execute-dynamic-code-rebuild.md @@ -1,314 +1,51 @@ -# execute-dynamic-code rebuild - -This document describes the rebuilt `execute-dynamic-code` pipeline after the layering refactor. - -The whole pipeline lives under `Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/` in the -`UnityCLILoop.FirstPartyTools.Editor` assembly. The `Entry` / `UseCase` / `Infrastructure` layers -below are the layering inside that tool plugin, not the onion assemblies around it. - -## Layered overview - -```mermaid -flowchart TD - subgraph Entry["Entry layer"] - Tool["ExecuteDynamicCodeTool"] - end - - subgraph UseCase["UseCase layer"] - ExecuteUseCase["IExecuteDynamicCodeUseCase / ExecuteDynamicCodeUseCase"] - end - - subgraph Infrastructure["Infrastructure layer"] - subgraph RuntimeAccess["Runtime access module"] - RuntimeContract["IDynamicCodeExecutionRuntime"] - RuntimeFacade["DynamicCodeExecutionFacade"] - Scheduler["DynamicCodeExecutionScheduler"] - ExecutorPoolContract["IDynamicCodeExecutorPool"] - ExecutorPool["DynamicCodeExecutorPool"] - Provider["RegistryDynamicCodeExecutorFactory"] - end - - subgraph Warmup["Warm-up module"] - WarmupRunner["DynamicCodeForegroundWarmupRunner"] - WarmupState["DynamicCodeForegroundWarmupState"] - Probe["ExecuteDynamicCodeReadinessProbe"] - Snippets["DynamicCodeForegroundWarmupSnippets"] - end - - subgraph Invocation["Invocation module"] - ExecutorContract["IDynamicCodeExecutor"] - Executor["DynamicCodeExecutor"] - InvokerContract["ICompiledCommandInvoker"] - Runner["CommandRunner"] - EntryResolver["CompiledCommandEntryPointResolver"] - end - - subgraph CompilationPipeline["Compilation pipeline module"] - CompilerContract["IDynamicCompilationService"] - Compiler["DynamicCodeCompiler"] - end - - subgraph Planning["Planning module"] - PlannerContract["IDynamicCompilationPlanner"] - Planner["DynamicCompilationPlanner"] - Plan["DynamicCompilationPlan"] - SourcePrepSvc["DynamicCodeSourcePreparationService"] - SourcePrep["DynamicCodeSourcePreparer"] - end - - subgraph BackendBuild["Backend build module"] - BuilderContract["ICompiledAssemblyBuilder"] - Builder["CompiledAssemblyBuilder"] - RefSvc["DynamicReferenceSetBuilderService"] - RefBuilder["DynamicReferenceSetBuilder"] - AutoUsing["PreUsingResolver / AutoUsingResolver / AssemblyTypeIndex"] - Diagnostics["CompilerDiagnostics"] - Backend["DynamicCompilationBackend"] - PathSvc["ExternalCompilerPathResolutionService"] - PathResolver["ExternalCompilerPathResolver"] - Roslyn["RoslynCompilerBackend"] - Worker["SharedRoslynCompilerWorkerHost"] - Fallback["AssemblyBuilderFallbackCompilerBackend"] - end - - subgraph SafetyLoad["Safety + load module"] - LoadContract["ICompiledAssemblyLoader"] - LoadSvc["CompiledAssemblyLoadService"] - Loader["CompiledAssemblyLoader"] - Cache["CompilationCacheManager"] - Timing["DynamicCompilationTimingFormatter"] - end - end - - Tool --> ExecuteUseCase - - ExecuteUseCase --> RuntimeContract - ExecuteUseCase --> WarmupRunner - ExecuteUseCase --> WarmupState - WarmupRunner --> RuntimeContract - WarmupRunner --> Probe - Probe --> Snippets - RuntimeContract --> RuntimeFacade - - RuntimeFacade --> ExecutorPoolContract - RuntimeFacade --> Scheduler - ExecutorPoolContract --> ExecutorPool - ExecutorPool --> Provider - Provider --> ExecutorContract - ExecutorContract --> Executor - Executor --> CompilerContract - CompilerContract --> Compiler - Executor --> InvokerContract - InvokerContract --> Runner - Runner --> EntryResolver - - Compiler --> PlannerContract - PlannerContract --> Planner - Planner --> Plan - Planner --> SourcePrepSvc - Compiler --> BuilderContract - BuilderContract --> Builder - Compiler --> LoadContract - LoadContract --> LoadSvc - Compiler --> Cache - Compiler --> Timing - - SourcePrepSvc --> SourcePrep - RefSvc --> RefBuilder - RefBuilder --> AutoUsing - LoadSvc --> Loader - Backend --> PathSvc - Backend --> Roslyn - Backend --> Fallback - PathSvc --> PathResolver - Roslyn --> Worker -``` - -## Composition graph - -```mermaid -flowchart TD - Static["DynamicCodeServices (static facade)"] - Services["DynamicCodeServicesRegistry"] - SourcePrepSvc["DynamicCodeSourcePreparationService"] - EntryResolver["CompiledCommandEntryPointResolver"] - CompilerFactory["DynamicCodeCompilationServiceFactory"] - Provider["RegistryDynamicCodeExecutorFactory"] - ExecutorPool["IDynamicCodeExecutorPool / DynamicCodeExecutorPool"] - RuntimeFacade["DynamicCodeExecutionFacade"] - Scheduler["DynamicCodeExecutionScheduler"] - ExecuteUseCase["ExecuteDynamicCodeUseCase"] - - Static --> Services - Services --> SourcePrepSvc - Services --> EntryResolver - Services --> Provider - Services --> ExecutorPool - Services --> RuntimeFacade - Services --> ExecuteUseCase - - Provider --> CompilerFactory - Provider --> SourcePrepSvc - Provider --> EntryResolver - ExecutorPool --> Provider - RuntimeFacade --> ExecutorPool - RuntimeFacade --> Scheduler - ExecuteUseCase --> RuntimeFacade -``` - -The registry only knows the runtime-access collaborators above. `DynamicCodeCompilationServiceFactory` -just returns a `DynamicCodeCompiler`, whose default constructor is what builds the planning, -backend build, and safety/load collaborators (`DynamicCompilationPlanner`, -`CompiledAssemblyBuilder` with its path/reference/backend services, and -`CompiledAssemblyLoadService`). None of those are visible to the registry. - -## Reading guide - -1. Start with `Entry layer`. - - `ExecuteDynamicCodeTool` only delegates the tool workflow. -2. Move to `UseCase layer`. - - `ExecuteDynamicCodeUseCase` owns the user-facing workflow for execute-dynamic-code, including the - foreground warm-up fallback that protects the first real execution after startup or reload. -3. Only then read `Infrastructure layer`. - - `Runtime access module` is the only runtime-facing gateway. - - `Warm-up module` keeps every warm-up entrypoint on the same snippets and request shape. - - `Planning`, `Backend build`, `Safety + load`, and `Invocation` split the heavy mechanics into named modules. -4. Read `Composition graph` last. - - `DynamicCodeServicesRegistry` is the only place that is expected to know many concrete classes at once. - - If a concrete-to-concrete edge only appears there, it is a wiring edge rather than a runtime dependency. +# execute-dynamic-code architecture notes + +The `execute-dynamic-code` pipeline lives under +`Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/` in the +`UnityCLILoop.FirstPartyTools.Editor` assembly. The layer names below describe the layering +inside that tool plugin, not the onion assemblies around it. + +This document deliberately carries no structure diagram and no per-class list. Those duplicate +what the code already states and rot on every refactor; the types themselves carry their own +`/// ` and `// Why` comments. What follows is only what reading the code does not give +you: where to start, and the rules the boundaries were chosen to satisfy. + +## Reading order + +1. `ExecuteDynamicCodeTool` — delegates and nothing else. +2. `ExecuteDynamicCodeUseCase` — the user-facing workflow: request shaping, the missing-`return` + retry, the foreground warm-up fallback, and response shaping. +3. `IDynamicCodeExecutionRuntime` and its facade — the single gateway into infrastructure. +4. The infrastructure modules, in dependency order: runtime access, warm-up, planning, + backend build, safety + load, invocation. +5. `DynamicCodeServices` / `DynamicCodeServicesRegistry` last. It is the one place expected to + know several concrete classes at once, so a concrete-to-concrete edge that appears only there + is wiring, not a runtime dependency. Server startup and recovery do not enter this pipeline. `UnityCliLoopFirstPartyServerLifecycleBinding` -in `UnityCLILoop.CompositionRoot.Editor` is the readiness probe: it resets server-scoped services and -warms the project IPC transport with the internal `get-version` bridge command, so a user-disabled tool -cannot block startup. Dynamic-code warm-up is owned entirely by this pipeline — the use case's -foreground fallback, and the background prewarm inside `DynamicCodeExecutionScheduler`. - -## Layer responsibilities - -- `Entry layer` - - Translate external calls into use-case invocations. - - Avoid business workflow logic. - - Avoid reaching into executor or compiler wiring directly. - -- `UseCase layer` - - Own temporal cohesion. - - Decide the workflow order for the feature. - - Keep user-facing retry rules such as the missing-`return` retry in one place. - - Depend on runtime contracts instead of concrete infrastructure types. - -- `Infrastructure layer` - - Own the mechanics of execution, compilation, loading, caching, path discovery, and worker lifecycle. - - Keep low-level concerns isolated behind contracts and focused service classes. - -## Infrastructure module boundaries - -- `Runtime access module` - - Exposes `IDynamicCodeExecutionRuntime` to the use-case layer. - - Reuses a single executor through `IDynamicCodeExecutorPool`. - - Arbitrates foreground and idle-only execution through `DynamicCodeExecutionScheduler`. - -- `Warm-up module` - - Owns the snippet shapes every warm-up path compiles. - - Keeps foreground fallback and background prewarm on the same sequence, so whichever runs first - marks the same execution shape as ready. - - Holds the "already warmed" state outside the use case. - -- `Planning module` - - Turns `CompilationRequest` into `DynamicCompilationPlan`. - - Keeps wrapper generation and literal hoisting together. - - Prevents `DynamicCodeCompiler` from knowing preparation details. - -- `Backend build module` - - Takes a plan and produces `CompiledAssemblyBuildResult`. - - Owns reference resolution, auto-using retry, backend selection, temp artifact handling, and build timings. - - Hides Roslyn worker details from the compiler orchestration. - -- `Safety + load module` - - Loads DLL bytes only after build success. - - Keeps `Assembly.Load` isolated behind a focused loader service. - - Prevents backend code from depending on assembly-load mechanics. - -- `Invocation module` - - Executes the compiled wrapper method through `ICompiledCommandInvoker`. - - Keeps reflection-heavy entry-point resolution behind a focused facade. - - Lets `DynamicCodeExecutor` stay a thin bridge between compile and invoke. - -## Class responsibilities - -- `ExecuteDynamicCodeTool` - - Thin entry point for the CLI tool. - - Delegates the full workflow to `IExecuteDynamicCodeUseCase`. - -- `ExecuteDynamicCodeUseCase` - - Converts parameters into the runtime request. - - Performs the missing-`return` retry. - - Runs the foreground warm-up sequence before the first real foreground execution, and skips it for - compile-only and yield-to-foreground requests. - - Shapes `ExecutionResult` into `ExecuteDynamicCodeResponse`. - -- `IDynamicCodeExecutionRuntime` - - Contract between use cases and runtime infrastructure. - - Exposes `ExecuteAsync` for foreground work and `TryExecuteIfIdleAsync` for work that must not - displace a foreground request. - - Keeps use cases from depending on factory and executor wiring directly. - -- `DynamicCodeExecutionFacade` - - Reuses executors through `IDynamicCodeExecutorPool`. - - Routes both entrypoints through `DynamicCodeExecutionScheduler` so foreground work can preempt - background work. - - Hides provider, pool, and scheduling wiring from use cases. - -- `DynamicCodeForegroundWarmupRunner` - - Compiles the shared warm-up snippets in sequence, either as foreground work or as idle-only work. - - Exists so no warm-up path can report warm while a shape the user hits first is still cold. - -- `DynamicCodeExecutorPool` - - Owns executor reuse and disposal for the runtime access path. - - Keeps that caching concern out of the runtime facade itself. - -- `RegistryDynamicCodeExecutorFactory` - - Builds `DynamicCodeExecutor` and `CommandRunner` from registered compiler services. - - Lives in the composition graph and runtime infrastructure, not in the entry layer. - -- `DynamicCodeExecutor` - - Bridges compilation and execution. - - Merges timing information. - - Converts hoisted literals into execution parameters. - -- `DynamicCodeCompiler` - - Orchestrates cache lookup, source security, planning, build, and assembly load. - - Depends on module facades instead of low-level helpers directly. - -- `DynamicCompilationPlanner` - - Produces the normalized `DynamicCompilationPlan`. - - Keeps request normalization and source preparation together. - -- `CompiledAssemblyBuilder` - - Builds the assembly bytes from a plan. - - Owns the auto-using retry loop and ambiguity rollback. - -- `DynamicCodeSourcePreparationService` / `DynamicCodeSourcePreparer` - - Normalize snippets into wrapper code. - - Handle top-level mode, return completion, and literal hoisting. - -- `DynamicCompilationBackend` - - Chooses between the Roslyn path and the AssemblyBuilder fallback path inside the build module. - -- `RoslynCompilerBackend` / `SharedRoslynCompilerWorkerHost` - - Provide the fast path with the shared external worker. - -- `CompiledAssemblyLoadService` / `CompiledAssemblyLoader` - - Keep metadata validation, assembly loading, and IL validation together. - -- `CommandRunner` / `CompiledCommandEntryPointResolver` - - Execute the compiled wrapper method while hiding reflection-heavy lookup. - - Form the `Invocation` facade seen by `DynamicCodeExecutor`. +in `UnityCLILoop.CompositionRoot.Editor` is the readiness probe: it resets server-scoped services +and warms the project IPC transport with the internal `get-version` bridge command, so a +user-disabled tool cannot block startup. Dynamic-code warm-up is owned entirely by this pipeline. + +## Why the module boundaries sit where they do + +- **Runtime access** is the only runtime-facing gateway, so use cases never reach into factory or + executor wiring. It also arbitrates foreground against idle-only work, which is what lets a real + user request preempt background compilation. +- **Warm-up** exists so every entrypoint compiles the same snippet shapes. Split them and one path + can report warm while the shape the user hits first is still cold. +- **Planning** keeps wrapper generation and literal hoisting together, so the compiler never learns + source-preparation details. +- **Backend build** owns reference resolution, auto-using retry, and backend selection, hiding + Roslyn worker lifetime from compiler orchestration. +- **Safety + load** loads DLL bytes only after a successful build, keeping `Assembly.Load` behind + one service so backend code cannot depend on assembly-load mechanics. +- **Invocation** hides reflection-heavy entry-point resolution, letting the executor stay a thin + bridge between compile and invoke. ## Design intent -- Make the architecture readable as `Entry -> UseCase -> Infrastructure`. - Keep the runtime dependency chain narrower than the composition graph. -- Allow the composition root to know concrete classes, while runtime layers depend on contracts or use cases. -- Keep the `Infrastructure` layer readable as a set of named module facades instead of one large helper cluster. +- Let the composition root know concrete classes; keep every runtime layer on contracts or use cases. - Keep the async-only contracts honest. -- Keep performance work in infrastructure without leaking that complexity into the entry layer. +- Keep performance work inside infrastructure and out of the entry layer. From 544eab54c6c5f35c1857c008b98a189f8703f778 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 20:58:18 +0900 Subject: [PATCH 8/9] docs: remove the execute-dynamic-code design memo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file was written as the design writeup for PR #901, "Rebuild execute-dynamic-code with shared Roslyn compilation and layered architecture" — the "rebuild" in its name was that PR's, not a description of its subject. Its only inbound link was the Windows handoff note created alongside it, and both were artifacts of that one migration. In the three months since, it was never updated on purpose: each of its three edits came from a rename sweep that happened to touch it. Reducing it to the parts code cannot state left almost nothing standing. The warm-up rationale it carried is already in the source three times over (DynamicCodeForegroundWarmupRunner, ExecuteDynamicCodeReadinessProbe). The server-readiness explanation is in UnityCliLoopFirstPartyServerLifecycleBinding's own summary. The reading order it prescribed is what following the delegation from ExecuteDynamicCodeTool gives you anyway, and the dependency-direction rules are enforced by the asmdef dependency tests rather than by prose. A finished migration's design memo left in the tree gets read as a description of current behavior. This one had already required two rounds of correction for exactly that reason, which is the same failure the other plan documents removed in this branch showed. docs/architecture/ is now empty and goes away with it. --- .../execute-dynamic-code-rebuild.md | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 docs/architecture/execute-dynamic-code-rebuild.md diff --git a/docs/architecture/execute-dynamic-code-rebuild.md b/docs/architecture/execute-dynamic-code-rebuild.md deleted file mode 100644 index 2f5022dc1f..0000000000 --- a/docs/architecture/execute-dynamic-code-rebuild.md +++ /dev/null @@ -1,51 +0,0 @@ -# execute-dynamic-code architecture notes - -The `execute-dynamic-code` pipeline lives under -`Packages/src/Editor/FirstPartyTools/ExecuteDynamicCode/` in the -`UnityCLILoop.FirstPartyTools.Editor` assembly. The layer names below describe the layering -inside that tool plugin, not the onion assemblies around it. - -This document deliberately carries no structure diagram and no per-class list. Those duplicate -what the code already states and rot on every refactor; the types themselves carry their own -`/// ` and `// Why` comments. What follows is only what reading the code does not give -you: where to start, and the rules the boundaries were chosen to satisfy. - -## Reading order - -1. `ExecuteDynamicCodeTool` — delegates and nothing else. -2. `ExecuteDynamicCodeUseCase` — the user-facing workflow: request shaping, the missing-`return` - retry, the foreground warm-up fallback, and response shaping. -3. `IDynamicCodeExecutionRuntime` and its facade — the single gateway into infrastructure. -4. The infrastructure modules, in dependency order: runtime access, warm-up, planning, - backend build, safety + load, invocation. -5. `DynamicCodeServices` / `DynamicCodeServicesRegistry` last. It is the one place expected to - know several concrete classes at once, so a concrete-to-concrete edge that appears only there - is wiring, not a runtime dependency. - -Server startup and recovery do not enter this pipeline. `UnityCliLoopFirstPartyServerLifecycleBinding` -in `UnityCLILoop.CompositionRoot.Editor` is the readiness probe: it resets server-scoped services -and warms the project IPC transport with the internal `get-version` bridge command, so a -user-disabled tool cannot block startup. Dynamic-code warm-up is owned entirely by this pipeline. - -## Why the module boundaries sit where they do - -- **Runtime access** is the only runtime-facing gateway, so use cases never reach into factory or - executor wiring. It also arbitrates foreground against idle-only work, which is what lets a real - user request preempt background compilation. -- **Warm-up** exists so every entrypoint compiles the same snippet shapes. Split them and one path - can report warm while the shape the user hits first is still cold. -- **Planning** keeps wrapper generation and literal hoisting together, so the compiler never learns - source-preparation details. -- **Backend build** owns reference resolution, auto-using retry, and backend selection, hiding - Roslyn worker lifetime from compiler orchestration. -- **Safety + load** loads DLL bytes only after a successful build, keeping `Assembly.Load` behind - one service so backend code cannot depend on assembly-load mechanics. -- **Invocation** hides reflection-heavy entry-point resolution, letting the executor stay a thin - bridge between compile and invoke. - -## Design intent - -- Keep the runtime dependency chain narrower than the composition graph. -- Let the composition root know concrete classes; keep every runtime layer on contracts or use cases. -- Keep the async-only contracts honest. -- Keep performance work inside infrastructure and out of the entry layer. From 48e9d4a8fd48445266e67164502a6c4edfc4794d Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 25 Jul 2026 23:44:07 +0900 Subject: [PATCH 9/9] docs: link the reference docs from AGENTS.md Seven files under docs/ had no path from AGENTS.md, so an agent reading only the repository guidelines never learned they existed. Rather than append a flat index, state each doc's rule where it fires: the glossary next to the naming policy, action SHA pinning under CI automation language, and new sections for the complexity threshold and broken-release recovery. Add a docs/ entry to the repository map that also covers docs/adr/, which had none. docs/dispatcher-pin-release-order.md stays reachable through docs/project-runner-pin.md and needs no separate pointer. --- AGENTS.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index c9447359a2..45301ec4a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,12 +18,19 @@ Directory-level responsibilities. Kept deliberately coarse — check the directo - `cli/common/` — Go modules shared by dispatcher and project runner. Tool parameter schemas live in `cli/common/tools/default-tools.json`; skill discovery in `cli/common/skillscan/`. - `cli/release-automation/` — Go logic backing GitHub Actions release/CI workflows. - `tools/UnityCliLoop.DeadCodeScanner/` — the C# dead-code scanner described below. +- `docs/` — reference docs for the rules in this file. `docs/adr/` holds architecture decision records: read the relevant one before reopening a settled design question, and note its stated reversal condition rather than relitigating from scratch. - `dist/` — locally built development binaries (`dist/darwin-arm64/uloop`, etc.); never committed. - `.claude/`, `.agents/` — generated skill copies; never edit directly (see Generated Skill Files). - `.uloop/` — runtime state and command outputs (screenshots, test results, hierarchy dumps). Do not rename public package, assembly, or extension API identifiers as part of cleanup-only changes. +Use the terms in `docs/glossary.md` with the meanings defined there — in code identifiers, docs, +commit messages, and reviews alike. Tool, internal bridge command, server, dispatcher, project +runner, pin, protocol version, skill, and pause point all have exact meanings there. When an +internal identifier conflicts with the glossary, rename the identifier; when a public one does, +keep it and record the mismatch in the glossary instead. + Comments in the code, commit messages, PR titles, and PR descriptions must all be written in English. Every test method must have a short comment that states what behavior the test verifies. @@ -62,6 +69,11 @@ These files are generated copies. Update the source skill definitions instead, t Write GitHub Actions and release automation logic in Go when it needs JSON parsing, workflow polling, state transitions, or non-trivial branching. Shell scripts are acceptable only as thin wrappers or simple command sequences. +Remote actions in workflow files must be pinned to full 40-character commit SHAs — version tags +such as `actions/checkout@v6` are rejected by CI. Before adding or updating any `uses:` ref, read +`docs/github-actions-security.md` (SHA resolution for nested action paths, the `setup-go` cache +ban on pull-request workflows, and the Unity license guard on cache steps). + ## Shared Release Inputs and Triggers All three components release through release-please; `dispatcherVersion` and component @@ -72,6 +84,15 @@ inputs outside the package roots (non-test `cli/common/**` sources, `scripts/ins run in the same PR; CI (`check-release-triggers`) fails otherwise. Rules and rationale: `docs/shared-release-inputs.md`. +## Broken CLI Releases + +When a native CLI release has a tag commit that differs from the `sourceRepositoryDigest` in its +asset attestations, the dispatcher refuses to download it. Roll forward to the next version — +never retag, rerun, or otherwise revive the broken one; a new run can only attest its own head +commit. Diagnosis commands, the narrow conditions under which rerunning *is* valid, and the +deliberately preserved mismatch specimen (`uloop-project-runner-v3.0.0-beta.48`, which must not +be deleted): `docs/release-recovery-runbook.md`. + ## Windows Compatibility Guardrails Most day-to-day development happens on macOS, but this project must keep working on Windows. @@ -87,6 +108,14 @@ apparently unreferenced type must stay, run the scanner and interpret its output as described in `docs/dead-code-scanner.md` (commands, and what `KeptByUnityOrReflection` / `PublicCandidate` do and do not prove). +## Code Complexity + +The repository-wide maximum cyclomatic complexity is 15, enforced by `cyclop` for Go and CA1502 +for C#. The `Code Complexity` workflow reports findings but never fails the build, so a finding +only does its job if someone acts on it: when you touch a reported function, reduce its +complexity before adding behavior. Commands and the two places the threshold is declared: +`docs/code-complexity.md`. + ## Native Go CLI Validation When running `uloop` commands for this project during CLI development, do not use the `uloop`