Skip to content

Releases: german-krasnikov/unity-biome-mcp

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 07:29
c96b187

[v2.1.0] — 2026-09-25

Changed

  • CI secret gating: Unity and Sonar steps skip with notice on fork/Dependabot PRs (no secrets); on push/schedule/dispatch, missing secrets cause exit 1 error — distinguishes "unavailable by design" from "secrets lost" per event type and job.
  • Python CI on every PR: Lint/Test/README check now run on every pull request — the path filter on pull_request was removed, so docs-only PRs also report these checks (prerequisite for required status checks).
  • Server dependencies via uv and Dependabot: Switched from pip to uv ecosystem. server/uv.lock is enforced source of truth — Lint job installs ruff directly from lock (via uv export --locked --extra dev), Test/README/badge jobs install from pyproject.toml ranges. Dependabot keeps both files in sync weekly. Lock validated via uv lock --check in Lint.
  • Dev dependency group unified: [dependency-groups].dev now self-references [project.optional-dependencies].dev (single list) — uv sync/uv run no longer silently drop hypothesis/pytest-cov/pyyaml/ruff.
  • SonarCloud action pinned by SHA: SonarSource/sonarcloud-github-action pinned to v5.0.0 (ffc3010...) instead of floating tag.
  • Unity-compat summary accuracy: No longer reports "Compile check passed" when compile was skipped due to missing secrets.

Fixed

  • SIGTERM supervisor test race: Child Python process now inherits SIG_IGN from parent (set before fork/exec) instead of racing to set it within 0.1s timeout.
  • Import-linter synthetic fixtures: Grimp cache now isolated via --no-cache flag (was caching by package name + mtime in shared CWD, polluting parallel test runs on Windows).
  • Ruff version pinned and SIM117 fixed: CI was installing uncontrolled ruff; pinned to 0.16.8 in server/uv.lock and installed from lock in Lint job. Ruff 0.16.8 found two real SIM117 violations (nested async with) in the codebase — fixed.

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 08:29
661c671

[v2.0.0] — 2026-09-08

Added

  • Offline Freshness and Reload Contract Validation (N0a/Reload): Two new NUnit dotnet projects (Tests~/AssemblyFreshness/ and Tests~/SourcePatchReadiness/) validate bytecode freshness detection and reload-readiness state transitions without Editor. Both use ~ folder convention to remain invisible to asset importer. Covers AssemblySourceFreshness byte comparison and SourcePatchReloadAck lease lifecycle.
  • Offline Adapter Compile-Contract (C1): Pure dotnet test project Tests~/MutationAdapterContract/ validates the SourcePatch seam contract and adapter Apply outcomes without Unity Editor. Executes on every build via ci-pure-dotnet.yml. Script-fetched adapter sources verified by SHA256 atomic lock in scripts/gauntlet/fetch_adapter_sources.py; seam-drift negative control confirms renamed seam members break the build.
  • Mutation Regression Test Lane (A5–A8): Comprehensive off-live Python test suite (server/tests/mutation/) covering FSR provider lifecycle, source-patch adapter, and reload recovery in isolation. Stories S11–S16c validate explicit disable, source restore, provider absence, re-add, and removal workflows; harness awaits Editor ready state before guard-vulnerable calls. Run via pytest tests/mutation -m mutation_live or scripts/run_mutation_regression_cell.py --mode full (driver 1800 s budget). CI lane .github/workflows/mutation-regression.yml supersedes legacy fsr-qualification.yml.
  • Provider Lifecycle Regression Tests (S16b–S16c): Remove/re-add FastScriptReload provider package in a live session; verify compile clean, provider state transitions, no orphan port/state files, epoch monotonicity, and port persistence after reload.
  • SDK Qualification Tests for Playtest Outcomes (SDK-E04): New server/tests/live/test_sdk_playtest_outcomes.py and test_sdk_scene_cache.py with sdk_tools and sdk_runtime fixtures validate run_playtest verdict accuracy across sync/async routes and cache invalidation after scenario terminal events.

Fixed

  • Batch Body-Line Normalization: Every successful batch command now emits [N] ok: <data> instead of a bare [N] ok when there was no data to report — Python consumers that already parse the ok: prefix now see a consistent format on every body line. Error lines ([N] err: ...) are unchanged.
  • Playtest Play/Stop Reconciliation After a Lost Acknowledgement: If the acknowledgement for an editor play/stop request is lost (connection drops right as the write lands), the outcome is now reconciled by polling the retry-safe get_status read until the expected playing= state is observed, instead of surfacing the connection error to the caller — the play/stop command itself is never resent. This relies on the bridge's reload guard already letting read-only status probes (get_status, compile_status, sync_status) through during a domain reload, so reconciliation can complete without waiting out the full reload-expiry window.
  • N1b — sync/force_refresh Registered as Mutating, Non-Batchable: The sync and force_refresh wire commands defaulted mutating/notBatchable to false at registration, so the read-only gate, Play Mode mutation gate, and batch safety check never saw them as unsafe — they executed and triggered Refresh/RequestScriptCompilation from live Play Mode or read-only state instead of being refused pre-dispatch. Both are now registered mutating: true, notBatchable: true; a batch line naming either is refused instead of executed, and in ask chat mode they are blocked by SessionAuthorization as a secondary defence (the Python sync_unity broker already blocks direct calls as a write). sync_status is unaffected and stays a read allowed during compile.
  • A8 — sync_unity Compile-Guard Absorption: When Editor auto-refresh starts compiling before sync command arrives, the C# guard rejection "Unity is compiling. Retry in 5s." is now absorbed by sync_unity. Instead of surfacing as an unhandled error, _sync_unity recognizes the guard text (constant SYNC_COMPILE_GUARD_TEXT), adopts the sync epoch, and runs the same wait/verdict path as a normal acknowledged sync. Any other error text propagates unchanged. Affects agent visibility: no more unhandled "Unity is compiling" when auto-refresh started compiling first.
  • N0b — Playtest Outcome Raising Contract: run_playtest now raises ToolError with the full report text for ANY non-pass outcome (fail/aborted/malformed/empty) on both sync and async routes. Pass outcome returns normally (text or JSON format). Eliminates silent failure absorption and provides consistent error signal across all dispatch paths.
  • N0a — Cache Trust Guard During Async Playtest (Middleware._scenario_uncertain): Set on start_playtest ack, cleared only by terminal evidence (is_scenario_terminal) or Edit Mode transition. While set, PrefetchCache lookups are bypassed and background prefetch is suppressed. Survives reconnect and reset_session() (reconnect does not prove playtest stopped). Fallback _force_scene_invalidate clears caches but not the guard.
  • N4a — Chat Ask Atomicity: PendingAskRegistry.Complete now performs atomic TryRemove with TCS captured before onAskEvent. Fail-soft try/catch added to ChatBackendProbe.IsChatBackendRunning and ChatSettingsHook methods to prevent stale probe exceptions from blocking ask dispatch.

Breaking

  • N1a — Plugin Registration Guards: A Python plugin declaring a reserved builtin name, a name colliding with a host tool, or a name it does not own (declared via register_read_cmds/register_write_cmds but never registered as a tool) is now rejected ENTIRELY — zero commands, gating entries, or budget features survive. Previously such a duplicate/foreign name was silently skipped, letting the rest of the plugin load. On the C# side, CommandRegistry.Register refuses a duplicate command from a different plugin instance and PluginRegistry.RegisterAllPlugins rolls back that plugin's entire registration; a plugin ID conflict (PluginRegistry.Register) refuses a different instance under an existing ID while re-registering the same instance stays idempotent. Failures are queryable via get_failed_plugins() (Python and C#, mirrored) and logged at registration time. Plugin command inventory is now attributed by the registering plugin's identity instead of name prefix, so AdditionalCommands and prefix-free commands are correctly grouped under their owning plugin in discover_tools/build_help output. Plugins that previously loaded with a silently-skipped duplicate must be fixed to use non-colliding, owned names.

Changed

  • CI Unity Version Matrix Refresh: Canonical worker baseline bumped 6000.0.65f1 → 6000.0.83f1 (LTS latest patch). Compat matrix (unity-compat.yml) gains a 6000.6 compile-check cell (previously missing) and its 6.7 alpha pin moves 6000.7.0a4 → 6000.7.0a6. FSR/mutation qualification lock (scripts/fsr_qualification_lock.json u_min) stays frozen at 6000.0.65f1 for this release's already-qualified mutation evidence; re-qualification on the new baseline is tracked as follow-up.
  • UPM Package Dependency: unity-plugin/package.json now declares com.unity.nuget.mono-cecil 1.11.5 as required dependency. Ensure project registry resolves com.unity.nuget.* packages (standard Unity configurations include this by default). Used internally for assembly analysis during reload and compile verification.

Known Issues

  • FSR Fork Adapter Object Leak: The FastScriptReload fork adapter (BiomeSourcePatchDispatcher) leaks one HideAndDontSave GameObject per enable→patch→disable cycle in mutation-mode sessions. No functional impact; the leak is confined to disposable mutation workers. Fix pending upstream in the provider fork (tracked as an imperative xfail, S16).
  • sync_unity Post-Resolve Settle Time: After a Package Manager resolve, sync_unity may take up to ~80 s to settle while Unity finishes asset-importing before the compile/reload verdict is available. Latency only, not a correctness issue.
  • AssetDatabaseHelper.WriteText UTF-8 BOM: Text written via the write_text MCP tool (.cs/.shader/.txt files) is encoded with a UTF-8 BOM instead of JsonHelper.Utf8NoBom. Unity and most tooling handle a BOM-prefixed file correctly; no functional impact observed.

v1.54.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 09:29
2f7968d

[v1.54.0] — 2026-09-06

Added

  • Modular Architecture Phase 2 — Plugin Registration Atomicity (F3): Failed plugin registration no longer leaves stale callable commands, gating entries, or budget features. Python: _atomic.py snapshots and restores all registries on failure. C#: PluginRegistry.CaptureForTest / RestoreForTest per-plugin; CommandRegistry.Register throws on duplicate when CallerIsPlugin. Invariant: a plugin that throws during register() leaves zero observable side effects
  • Modular Architecture Phase 2 — Self-Contained Module Pilot (PR-04): WatchModule owns its registration metadata via declarative SPEC_KWARGS merged at import time. Instance-scoped _send/_args injection proves module isolation. C# CommandRegistry.Entry gains MutatingArgsPolicy delegate and NotBatchable bool; uitk_file hardcoded name-switch migrated to registration site. 36 policy-vector tests cover read/write, batch, and retry-safety for 5 representative commands. Invariant: adding a command requires only the module's registration — no central list edits
  • Modular Architecture Phase 2 — Reload Module Facade with Replaceable Algorithm (PR-04R): SyncHelper.IsMainAssemblyCompiling is now an injectable predicate (the identified architectural seam). SourcePatchModePolicy.RequestDisable() delegates via ISourcePatchReloadPort instead of direct SyncHelper.TriggerSync call. Import-linter forbids diagnose/reload_ladder from importing sync.py. Invariant: Reload algorithm can be swapped via composition without edits to core, transport, Chat, or SourcePatch
  • Modular Architecture Phase 2 — Chat Off as Real Disconnection (PR-05): Removed stale reflection probes in ChatBackendProbe/ChatSettingsHook; Chat assemblies now wire owned delegates via [InitializeOnLoad]. ask_user returns immediately when no Chat subscriber exists (no 300s hang). search_context throws ProviderUnavailableException (classified UNAVAILABLE, logged as warning) when Chat.CLI absent. Nine Chat-dependent test files moved from base to Chat.Tests.CLI/View; base asmdef no longer references Chat. Invariant: Chat Off/absent leaves MCP tools fully operational; no stale process, pending-ask leak, or false-positive error
  • Modular Architecture Phase 2 — SourcePatch Off/Absence Proof & Reload Boundary (PR-06): ForceUnreconciledForTests() seam on SourcePatchHost enables contract tests. Two C# tests prove real lazy-reconciliation to Off and concurrent-sync epoch drift resolves to Recovery (not false-Off). Python boundary scan locks SyncHelper.TriggerSync out of SourcePatch control files except sanctioned adapter. Invariant: SourcePatch Off leaves normal writes and domain reload unintercepted; provider/algorithm swaps without core changes
  • DSL Conformance Oracle Strengthened (PR-07): Strict error classification in DSL harness. Fixed ci-conformance.yml lane-generation to use gen_lane_args.py. Add zero-selection fail-closed guard. Documented reserved header fields (NeedsPlaymode, ExpectSteps, ExpectFailed, SuiteOnly) as parsed but unenforced. New PreparedPlaytest immutable container for data isolation. ConsoleErrored field serialized in receipt ToJson (additive, backward-compatible). Invariant: DSL conformance tests classify errors specifically; prepared playtest data is immutable; lane selection cannot silently produce empty set
  • Coverage Hotspots and Clone Detection (PR-08): New opencover_parse.py (OpenCover XML → method-level cyclomatic complexity + coverage), coverage_hotspots.py (changed-aware top-20 report with scenario map), cochange_report.py (manual cochange analysis). Configure jscpd v5.1.2 with baseline-from-ref and fail-on-new-clones gate in ci-python.yml. Coverage-hotspots CI job reports to docs/quality/ on master push. Invariant: new substantive code duplicates fail CI; coverage hotspots ranked by changed × complexity × uncovered

Fixed

  • F7 — Honest Terminal Outcome for Playtest Verdict: PlaytestStepReceipt.Ok now accounts for console errors detected after the step passes. Both text and JSON verdict consumers reject ABORTED runs, receipts with contradictory aggregate/step ledgers, and empty/malformed required receipts. Invariant: abort, console-error-on-passing-step, cleanup failure, and empty receipt are non-PASS in all formats (text/JSON) and all paths (sync/async start-poll)
  • F8 — Unified Read-Only Policy Across All Routes: Remove _INTERNAL category filter from WRITE_CMDS that allowed start_playtest and source_patch_write to bypass read-only gate. Reclassify ping/get_version as explicit reads. Add unknown_is_write fail-closed fallback at both authorization call sites. Mirror start_playtest into SCENE_STATE_NEUTRAL_WRITES to match run_playtest. Invariant: read-only blocks mutating commands regardless of sync/async route, warmup state, or category
  • F1 — Player Profile Preflight Rejects Unsupported DSL Before Execution: PlayerProfilePreflight.Validate(ParseResult) in engine-free Playtest.Core checks fatal parse errors, @needs editmode header, SETUP/TEARDOWN blocks, EXPECT_FAIL, compound WAIT_UNTIL AND/OR, ASSERT...TIMEOUT retry semantics, and empty main section. PlayerPlaytestRunner.Run() wraps load+parse in try/catch and delegates gate decision to preflight. Invariant: unsupported Player scripts are rejected with terminal receipt before any body action executes
  • F2 — Conservative Scene Cache Invalidation After Playtest Scenario: Split SCENE_STATE_NEUTRAL_WRITES into two policies: "counts as blind write for advisory guard" (unchanged) vs. "can make caches stale" (new is_scenario_terminal predicate). Middleware.invalidate_scene_caches() uses _scene_generation counter to fence late in-flight background prefetches from re-poisoning cleared caches. Cover sync, async start/poll, suite, wire-exception, and poll-give-up paths. Invariant: a public read immediately after playtest scenario completion returns fresh scene state

Changed

  • Architectural Boundaries Established: Import-linter contracts mechanically prevent tools→server, tools→middleware, bridge→session, plugin_api→server imports. Static asmdef boundary checks for Playtest.Core (noEngineReferences) and SourcePatch (no provider refs). CI validates architectural integrity on every build
  • Hygiene: Replaced blocking .Result with await in CommandRouterAskUserTests (test-only)

Test Results

  • Modular architecture phase 2 suite: All policy vectors green; plugin atomicity verified; chat/sourcepatch boundaries confirmed
  • DSL conformance: Reserved field parsing confirmed; Player preflight gate validated

v1.53.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 15:58
0e06aa2

[v1.53.0] — 2026-09-05

Added

  • Async playtest dispatch (Wave E, E02–E04): Non-blocking start_playtest + polling get_playtest_run for scripts with timeout > 120s; threshold _RUN_PLAYTEST_SYNC_CEILING_S (Python) and RunPlaytestTimeoutSeconds (C#) prevent single blocking TCP calls from timeout. Transparent routing in run_playtest() via playtest_async.run_via_start_poll() (R-04); run ID persisted in PlaytestReceiptStore across domain reloads
  • PlayMode corpus and CI job (Wave E, E06a–E06b): 3 Play-bound .playtest files in unity-test-project/Assets/Tests/PlayMode/ via PlaytestCorpusPlayModeTests; CI lane unity-tests.yml PlayMode job with -testPlatform PlayMode --filter PlaytestCorpusPlayModeTests filter; validates runtime coroutines and Play-only DSL operations
  • Player fan-out runner (Wave E, E07–E09): New scripts/run_player_playtests.py with --jobs N parallelism; filters .playtest by @needs player tag; builds standalone Player for Linux/macOS/Windows; runs sequentially or in parallel; returns matrix report. 6 .playtest files in StreamingAssets/Playtests/ under CI nightly-full lane
  • Honest test coverage (Wave E, E11): Updated taxonomy and CI lanes: 9 EditMode corpus files + 3 PlayMode corpus files + 6 Player files = 18/22 total coverage. Python .suite lane stress-tests stateful A→B→C runner via run_playtest_suite(tag="@suite-only")
  • MCP verb DSL steps: MCP <cmd> key=value [INTO $var] executes MCP commands from playtest scripts; $RUN_ID injected as parallel-safe unique identifier per run; EXPECT_FAIL modifier inverts pass/fail for single step; format=json outputs structured per-step receipt objects with command, status, and response data persisted for archival
  • EditMode DSL execution: # @needs editmode header opt-out of Play Mode gate; PlaytestRunner.Run(..., requiresPlayMode: false) runs steps through EditorApplication.update ticks (not delayCall) via centralized MainThreadDispatcher; MCP mutations validated at dispatch time
  • Playtest corpus carriers: 9 Edit-capable MCPFeedbackFixture files run through PlaytestCorpusEditModeTests (B21); 3 Play-bound-only files (C_shared_finish, DSL_types, I3_independent_pass) run through PlaytestCorpusPlayModeTests (B22) for runtime coroutine validation; Python .suite lane stress-tests stateful A/B/C chain via run_playtest_suite(tag="@suite-only")
  • Test taxonomy and lanes (data-driven): Tests/taxonomy-map.json (C13) canonical cross-language taxonomy (pytest markers, C# TestCategories, DSL @needs values); Tests/biome-test-lanes.json (C15) 4 lanes (pr-python-core, pr-unity-core, master-conformance, nightly-full) with filter configs; scripts/check_test_metadata.py (C18) enforces unknown category/tag = CI failure
  • Test selection flags in runner: run_unity_tests.py now accepts --category and --assembly for targeted test runs; --tests-file allows filtering from a newline-delimited file with validation and deduplication by canonical form
  • Test run retention and pruning: TestRunStore.PruneOldRuns implements 50-run cap with 7-day staleness floor; runs older than 7 days are retained only if under 50 total
  • Dispatch-failure visibility: TestRunReconciler now surfaces the exact failure reason in dispatch_issue field instead of generic dispatch status
  • Tag filtering for playtest suites: run_playtest_suite(tag=...) filters files by # @tags header value; zero matches fail-closed
  • Cassette recorder for protocol testing: New bridge_cassette.py module records and replays TCP exchanges for deterministic wire protocol testing
  • Full baseline migration: full-baseline.json replaces hardcoded 6001 Unity version constant
  • Engine-free Core assembly (Wave D): New UnityMCP.Playtest.Core runtime assembly with noEngineReferences: true (D16) extracts parser, Compare logic, and utilities from Editor; consumed by both Editor playtest runner and Player runtime without Unity dependency. Enables pure dotnet testing lane and player builds.
  • Unified playtest parser (Wave D, D14, D18): PlaytestParser.cs moved from Editor to Core and split into logical files (Directives, Internals, Mcp, Subroutines). New PlaytestHeaderScanner parses DSL headers at load time. Unified Compare() logic serves Editor DSL, Player runtime, and pure dotnet tests.
  • Pure dotnet test lane (Wave D, D15): New unity-plugin/Tests~/Pure/UnityMCP.Playtest.Core.Tests.csproj runs NUnit tests outside Unity using dotnet test with zero dependency on Editor or runtime. Tilde folder prevents Microsoft.NET.Test.Sdk from breaking Editor compilation. Validates parser correctness and Compare parity.
  • Player runtime playtest support (Wave D, D17): PlayerPlaytestRunner reuses Core parser and validates scripts via pre-scan gate that rejects unsupported verbs (MCP, MOVE, CLICK) and # @needs editmode before execution. Supports 9 core step types: ASSERT, ASSERT_BATCH, ASSERT_NEAR, ASSERT_CONSOLE_CLEAN, WAIT, WAIT_UNTIL, LOG, SNAPSHOT, INVOKE.
  • Numeric utilities (Wave D): New Float3.cs immutable 3D vector type and NumericParsing.cs utilities for decimal/float parsing with culture-invariant rounding. IAliasSource interface (D20) allows custom alias providers to inject VAL definitions at runtime.

Changed

  • PR-lane Python test speed: 262s → 35s via selective test-class filtering and focused pytest markers (M-wave-a)
  • Test run polling compaction: get_test_run responses omit full detail for non-terminal states, keeping compact snapshots until terminal verdict
  • CI timeout consistency: Timeout-minutes now explicitly set across all job lanes; Linux-only Unity compile on PR lanes (v1.17.0 contracts)
  • Library/.meta cache strategy: Library artifacts cached across runs with mtime validation; .meta GUID conflicts pre-checked on import

Test Results

  • C# EditMode: 9,310 passed
  • Python PR-lane: 35s (35× speedup)

v1.52.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 17:26
c18b692

[v1.52.0] — 2026-09-03

Fixed

  • Test run timeout on Editor focus loss or domain reload: run_tests_wait/get_test_run no longer hang indefinitely when the Editor loses focus or undergoes a domain reload. Implemented disk-fallback protocol and health-value gates to detect stalled test dispatch and recover via direct test-run store reads; durable protocol now includes explicit health field and expected_count for safer gate validation
  • Zero-match filter produces silent failure or stuck Finalizing state: Running tests with a filter that matches no tests (e.g., typo in filter name) now returns an explicit ZERO_TEST_MATCH issue in the result, allowing the next run to proceed instead of silently hanging or remaining in Finalizing state; added 180-second ceiling on Finalizing expiry tied to execution boundary events
  • await_compile and get_compile_errors return stale cached results: Removed get_compile_errors from _READ_CACHEABLE and recompile from GATE_PRIORS so each call after a C# edit immediately polls live compilation status instead of returning outdated [CACHED] responses
  • Ghost Unity process appears alive to status checks: Compile paths (get_compile_errors, await_compile, sync_unity) now return explicit UNITY-UNREACHABLE error when the Editor process is hung or dead; mcp_status exposes unity_status=unreachable via improved liveness detection (~30-second ping intervals)
  • After Editor restart, Python client loops on connection refused when port changes: Added fast-path port rediscovery with explicit [port changed] warning when the Editor binds to a different TCP port after restart, eliminating repeated ConnectionRefused cycles; corrected off-by-one error in MCPServer.StartAsync bind-retry loop boundary so the final allowed retry actually attempts the original port instead of immediately falling back to free-port allocation
  • "Level Up!" button in Setup Wizard hangs on double-click and shows Unable to add package error: UPM operations now guarded by a single in-flight-state checker that disables the button during updates; safe to re-click immediately. UpmErrorClassifier and UpmOperationGuard prevent phantom "package add already running" errors
  • Plugin or server update overwrites manual changes to .env, custom config keys, or TOML sections: Configuration files now use atomic swap (File.Replace) instead of delete-then-move, preserving concurrent edits even under file-locking scenarios (Windows OneDrive/antivirus). Deep-merge strategy for TOML config; per-target LastSyncedVersion tracking prevents unnecessary rewrites
  • Random HTTP/TLS probes on the TCP port flood the console with repeating desync warnings: Added DesyncWarnLimiter to classify known foreign protocol probes (HTTP, TLS handshakes) and emit at most one warning per 30 seconds, reducing console noise
  • uitk_file action on Windows fails reading .uxml/.uss files with escapes Assets/ path validation error: Path escaping now correctly handles Windows backslashes (\) as part of the canonical path, not as escape sequences; fixed ToAbsPath resolution for relative UXML/USS includes
  • Setup Wizard enables configurations for all detected agent tools (VS Code, Codex) even if not installed: Wizard now only enables agent configs for tools that are actually installed, preventing stale or phantom agent entries in the MCP configuration

Changed

  • Main-thread dispatcher refactored to eliminate timeout windows: Replaced EditorTickOnce with a centralized MainThreadDispatcher that uses EditorApplication.update directly (not delayCall), making it immune to focus loss and domain reload hangups. Dispatcher runs snapshot-bounded Drain cycles with per-action exception handling; delayCall now restricted to GUI-only contexts (Chat, Wizard, menus)
  • Atomic file writes for all config and state files: MCP_Port.json, {pid}.port, editor state, wizard config, and durable test-run store now use AtomicFile.Swap (temp file + File.Replace) to prevent data loss under concurrent file-locking (Windows OneDrive/antivirus/network paths)
  • Durable test-run protocol gates for recovery: Added health (no_test_progress, editor_unresponsive) and expected_count fields to test-run results for safer terminal-state detection; ZERO_TEST_MATCH warning flags empty-filter matches. Disk fallback and lifecycle-gate self-heal allow runs to recover without stalling
  • Error classification on unreachable Editor: Compile paths return explicit UNITY-UNREACHABLE error instead of timeout; mcp_status reports unity_status=unreachable alongside liveness values (connected, connected-stalled, etc.)

Documentation

  • Updated AI/architecture.md to document MainThreadDispatcher, AtomicFile.Swap, durable test-run protocol gates, and error classification
  • Updated AI/structure.md with new C# modules: AtomicFile.cs, MainThreadDispatcher.cs (replacing EditorTickOnce), hygiene test guards, and desync limiters

Test Results

  • C# NUnit: 9,276 passed / 0 failed / 34 skipped (×2 runs, run-6bf0491c, run-9b12374c; health=healthy, build_coherent)
  • Python live: 278 passed / 0 failed
  • Python conformance/seams: 290 passed / 42 failed (pre-existing; byte-identical to master):
    • 41 failures: seams batch completeness/surface tests — BatchHelper.cs v1.41.0 (d3fc6c9) writes [N] {result} while test expects [N] ok:; unrelated to this fix
    • 1 failure: test_screenshot_succeeds — empty screenshot on background Editor; unrelated to this fix
    • Note: seams live suite does not run in CI (ci-conformance: -m "not live")
  • Repository unit: 1,180 passed / 1 failed (pre-existing: test_installed_runtime_lease.py::test_installed_entrypoint_serves_public_stdio — environment venv issue without mcp SDK, byte-identical to master)
  • Install unit: 89 passed / 1 skipped
  • Server non-live unit: 6,857 passed / 1 skipped / 634 deselected
  • Wire protocol: 26 passed
  • Code quality (ruff): All checks passed

v1.51.0 — Mutation Mode (Source Patch MVP)

Choose a tag to compare

@german-krasnikov german-krasnikov released this 01 Sep 09:43
10ee241

v1.51.0 — Mutation Mode (Source Patch MVP)

This release is dedicated to Mutation Mode — optional hot method-body patching for the Unity Editor. An AI agent (or you) can edit the body of a C# method and see the change live, with no domain reload, no lost state, no waiting.

What it does

While Mutation Mode is ON, asset(action="write_text") on a .cs file routes through an optional in-memory patching provider (a pinned fork of the excellent open-source FastScriptReload). The edited method's new body takes effect immediately in the running Editor: statics survive, scene state survives, iteration cost drops from a full reload cycle to effectively zero.

Quick start

  1. Install the provider (optional package; the base plugin never depends on it) — add to Packages/manifest.json:
    "com.handzlikchris.fastscriptreload": "https://github.com/german-krasnikov/FastScriptReload.git?path=/Assets#b90a5c3fd7cfa452f23e8a807cc7bd61dc934bbf"
  2. Enable — either editor(action="mutation_mode", enable=true) via MCP, or the new Mutation Mode (experimental) checkbox in MCP Settings Hub → General.
  3. Edit method bodies via asset(action="write_text") — applied instantly. Disable with enable=false (exactly one script reload, files stay as written).

Without the provider the checkbox is disabled with a tooltip and enabling returns a clear error — fail-closed by design.

Safety model

  • Fail-closed everywhere: invalid edits are rejected before any file effect; any uncertainty transitions to a Recovery state instead of guessing. Recovery now has a legal exit (enable=false → one reload).
  • Path validation: traversal (..), absolute paths, Packages/, non-.cs targets are rejected pre-effect (SourcePatchPathGuard).
  • State machine: Unavailable → Off ↔ OnReady/Busy ↔ Recovery, receipt-based reconciliation after every domain reload (mode auto-resets to Off — no stale sessions).
  • Transport: internal read-only wire commands are now correctly retry-safe; unsafe commands are never resent after uncertain delivery.

Qualified scope (MVP)

Existing sync non-generic methods in Assets/, body-only edits, single file at a time, plain C# classes (POCO), Edit Mode, Mono backend, Unity 6000.0.65f1.

Platforms: macOS ARM64 + Linux x64 (CI-qualified — two unchanged Aggregate-PASS runs of the qualification matrix); Windows x64 engineering-supported (CI qualification blocked by headed-GUI unavailability on hosted runners).

Not supported (yet): MonoBehaviour-derived method mutation, async/iterator/lambda/generic/overloaded methods, Play Mode mutations, multi-file edits. All are fail-closed, not silent.

Also in this release

  • UI: Settings Hub toggle with live state polling (Provider Absent / Busy / Disabling / Recovery / Play Mode disabled states, recovery warning).
  • Observability: mcp_status exposes source_patch_intent, source_patch_provider, source_patch_state, source_patch_op, source_patch_recovery.
  • Fixes: ON-path writes no longer trigger spurious [ScriptCompilation] requests (raw write instead of ImportAsset); AutoRefresh lease no longer leaks on Recovery entry; retry-safe classification for internal wire commands (get_status, sync_status).
  • Docs: new user guide docs/features/mutation-mode.md (workflow, provider install, limitations, FAQ) plus updated architecture/testing internals docs.

Verification

Release evidence: local qualification cycles + CI matrix runs (see AI/source-patch-release-evidence.md), live lane 278/0/0, server non-live lane 6630 passed, cross-project fault injection 4/4, full SourcePatch C# namespace 147/148 (1 pre-existing guard skip).

v1.50.8

Choose a tag to compare

@github-actions github-actions released this 23 Aug 22:18
3374c75

[v1.50.8] — 2026-08-23

Fixed

  • NoTestsMatched outcome rank: TestRunFinalizationCoordinator.OutcomeRank() and
    TestRunStore.IsTerminalOutcome() now recognise NoTestsMatched as a terminal outcome.
    Zero-test filter runs no longer downgrade to Incomplete and throw TestRunStoreException.

v1.50.7

Choose a tag to compare

@github-actions github-actions released this 23 Aug 20:44
f363d96

[v1.50.7] — 2026-08-23

Changed

  • Mutation Mode scope clarified (MCPAUDIT-007, MCPAUDIT-009): Error messages in
    BatchHelper.cs and CommandRouter.cs now redirect Play Mode mutation blocks to
    execute_code(persist_as=...). EditorStateHelper.cs no_hot_reload_package warning
    now states: Mutation Mode controls WHEN reload happens, not WHETHER — .cs edits still
    trigger a full domain reload without the Hot Reload package.

v1.50.6

Choose a tag to compare

@github-actions github-actions released this 23 Aug 14:53
d70fa9e

[v1.50.6] — 2026-08-23

Fixed

  • sync_unity false-green on warn: response (MCPAUDIT-015): Unity-side warn: and err: ack
    strings now raise ToolError immediately instead of silently falling through to _parse_ack.
    Prevents false "sync clean" when compilation is blocked (e.g. Play Mode script edit).
  • Non-string ack guard: sync_unity raises ToolError if bridge returns None (timeout
    sentinel) instead of propagating AttributeError from _parse_ack.
  • editor() schema exposes all actions (MCPAUDIT-004): action parameter now uses
    Literal["state","play","pause","stop","select","project_path","fast_play_mode","mutation_mode"]
    so schema-validating providers see the full enum.

v1.50.5

Choose a tag to compare

@github-actions github-actions released this 23 Aug 10:11
99df9a1

[v1.50.5] — 2026-08-23

Fixed

  • Status conflation resolved (WS-MCP-249): mcp_status.mutation_mode now reports
    MCPSettings.GetMutationMode() directly instead of HotReloadDetector.IsActive() — external
    AutoRefresh changes no longer falsely report Mutation Mode active.
  • FastPlay ownership (WS-MCP-249): FastPlayOwner flags enum (User=1, Mutation=2) tracks
    who owns the Play Mode setting — Mutation OFF no longer disables user-owned Fast Play Mode.
  • Python cache removed (WS-MCP-249): _mm_cached eliminated from sync.py and
    code_intel.py — every call re-reads get_status fresh.