# ArchitectureScope Container Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 按 `docs/specs/architecture-scope-container-implementation-spec.md` 实现统一的 `ArchitectureScope` / FlowScope Container / Scope Tree,并替换当前 `RootContext + FeatureScope` 试验入口。 **Architecture:** 第一阶段只做核心 Scope 与容器边界:`FlowScope.Create()` 创建 Root,Root / Feature / Child 都通过同一个 `IArchitectureScope` 表达。保留现有 `FlowScope.Container.Container` 中已经成立的父子查找、局部覆盖、`PerScope()`、Dispose 子树等能力,在其上新增 Core 接口与 `ArchitectureScope` 包装/约束层;旧 `FlowScope.Composition` 不继续扩展,最后删除或降级。 **Tech Stack:** Unity C#,NUnit EditMode tests,现有 `FlowScope.Runtime.asmdef` / `FlowScope.Tests.EditMode.asmdef`,验证优先使用 `dotnet build --no-restore`。 --- ## 执行边界 本计划只实现以下内容: - `FlowScope.Create()` 根入口。 - `IArchitectureScope` / `IScopeInfo` / `IFeature` / `IRegistrationBuilder`。 - `ArchitectureScope` 统一 Scope 节点。 - Register / Resolve / CreateScope / CreateFeature / Dispose 规则。 - 旧 Composition 测试中仍有价值的 Root 服务、Feature 隔离、命名注册用例迁移到 Core 测试。 - 移除或降级旧 `RootContext + FeatureScope + Command/Query` 试验入口。 本计划不实现: - Command / Query / Event。 - 完整 MVC / MVVM。 - UI Panel 生命周期统一接管。 - 自动扫描 Feature。 - 资源、存档、音频、UI 模块改造。 执行前必须先确认当前工作树已有改动,不能覆盖用户已有修改: ```powershell git status --short git diff -- "My project/Assets/FlowScope/Runtime/Container/Container.cs" git diff -- "My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs" git diff -- "My project/Assets/FlowScope/Tests/EditMode/Container/ContainerTests.cs" ``` 如果这些文件已有未合并改动,执行者必须在当前改动基础上续写,不得 `git restore` 或重写文件。 ## 文件结构 ### 新增 Runtime Core 文件 - Create: `My project/Assets/FlowScope/Runtime/Core/FlowScope.cs` - 静态入口,创建 Root Scope。 - Create: `My project/Assets/FlowScope/Runtime/Core/IArchitectureScope.cs` - 统一 Scope 主接口。 - Create: `My project/Assets/FlowScope/Runtime/Core/IScopeInfo.cs` - 只读 Scope 元信息接口。 - Create: `My project/Assets/FlowScope/Runtime/Core/IFeature.cs` - Feature 安装接口。 - Create: `My project/Assets/FlowScope/Runtime/Core/IRegistrationBuilder.cs` - Core 层注册生命周期 builder 接口。 - Create: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs` - 统一 Scope 节点实现。 - Create: `My project/Assets/FlowScope/Runtime/Core/Container/ScopeExceptions.cs` - 核心 Scope 异常类型。 ### 修改现有 Container 文件 - Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs` - 暴露 `ParentInfo` / `Path` / `IsDisposed` 所需的最小只读能力。 - 增加同级重名 Scope 检查。 - 确保 `CreateScope(string name)` 空名失败。 - 保持注册只写当前 Container,不改变父级。 - Modify: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs` - 将 `ServiceLifetime.Scoped` 命名调整为 `PerScope`,或在 Core 层适配为 `PerScope`。 - 保持 `PerScope` 缓存到 Resolve 发起 Scope 的语义。 ### 新增 Core EditMode 测试 - Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs` - Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs` - Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs` - Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeDisposeTests.cs` ### 删除或降级旧 Composition - Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs` - Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs` - Delete or migrate: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs` 如果执行时选择“降级”而不是删除,必须放到 `FlowScope.LegacyComposition` 命名空间,并且不得被新 Core 测试依赖。 ## Task 1: 新增 Core 接口与根入口 **Files:** - Create: `My project/Assets/FlowScope/Runtime/Core/IScopeInfo.cs` - Create: `My project/Assets/FlowScope/Runtime/Core/IRegistrationBuilder.cs` - Create: `My project/Assets/FlowScope/Runtime/Core/IFeature.cs` - Create: `My project/Assets/FlowScope/Runtime/Core/IArchitectureScope.cs` - Create: `My project/Assets/FlowScope/Runtime/Core/FlowScope.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs` - [ ] **Step 1: 创建目录和 Unity meta** 执行者在 Unity 项目中创建目录: ```text My project/Assets/FlowScope/Runtime/Core/ My project/Assets/FlowScope/Runtime/Core/Container/ My project/Assets/FlowScope/Tests/EditMode/Core/ ``` 如果通过文件系统创建,执行后打开 Unity 或保持后续生成 `.meta` 被纳入同一提交。不要手写 `.meta`。 - [ ] **Step 2: 先写 Root 入口测试** 在 `ArchitectureScopeRegistrationTests.cs` 写入: ```csharp using NUnit.Framework; namespace FlowScope.Tests.EditMode.Core { public sealed class ArchitectureScopeRegistrationTests { [Test] public void Create_ReturnsRootArchitectureScope() { using var root = FlowScope.FlowScope.Create(); Assert.That(root.Name, Is.EqualTo("Root")); Assert.That(root.Parent, Is.Null); Assert.That(root.Path, Is.EqualTo("Root")); Assert.That(root.IsDisposed, Is.False); } } } ``` - [ ] **Step 3: 运行测试确认失败** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: FAIL,缺少 `FlowScope.FlowScope` / `IArchitectureScope`。 - [ ] **Step 4: 新增接口代码** `IScopeInfo.cs`: ```csharp namespace FlowScope { public interface IScopeInfo { string Name { get; } IScopeInfo Parent { get; } string Path { get; } bool IsDisposed { get; } } } ``` `IRegistrationBuilder.cs`: ```csharp namespace FlowScope { public interface IRegistrationBuilder { void AsSingleton(); void PerScope(); void AsTransient(); } } ``` `IFeature.cs`: ```csharp namespace FlowScope { public interface IFeature { void Install(IArchitectureScope scope); } } ``` `IArchitectureScope.cs`: ```csharp using System; namespace FlowScope { public interface IArchitectureScope : IScopeInfo, IDisposable { IArchitectureScope CreateScope(string name); IArchitectureScope CreateFeature() where TFeature : IFeature, new(); IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new(); IRegistrationBuilder Register(); IRegistrationBuilder Register(string name); IRegistrationBuilder Register() where TImplementation : TService; IRegistrationBuilder Register(string name) where TImplementation : TService; void RegisterInstance(TService instance); void RegisterInstance(TService instance, string name); TService Resolve(); TService Resolve(string name); bool TryResolve(out TService value); bool TryResolve(out TService value, string name); } } ``` - [ ] **Step 5: 新增临时可编译入口** `FlowScope.cs`: ```csharp using FlowScope.Core.Container; namespace FlowScope { public static class FlowScope { public static IArchitectureScope Create() { return Create("Root"); } public static IArchitectureScope Create(string rootName) { return new ArchitectureScope(rootName); } } } ``` - [ ] **Step 6: 暂存后续实现需要的空壳** `ArchitectureScope.cs` 先写可编译空壳,但未实现行为: ```csharp using System; namespace FlowScope.Core.Container { internal sealed class ArchitectureScope : IArchitectureScope { public ArchitectureScope(string name) { Name = name; Path = name; } public string Name { get; } public IScopeInfo Parent => null; public string Path { get; } public bool IsDisposed { get; private set; } public IArchitectureScope CreateScope(string name) => throw new NotImplementedException(); public IArchitectureScope CreateFeature() where TFeature : IFeature, new() => throw new NotImplementedException(); public IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new() => throw new NotImplementedException(); public IRegistrationBuilder Register() => throw new NotImplementedException(); public IRegistrationBuilder Register(string name) => throw new NotImplementedException(); public IRegistrationBuilder Register() where TImplementation : TService => throw new NotImplementedException(); public IRegistrationBuilder Register(string name) where TImplementation : TService => throw new NotImplementedException(); public void RegisterInstance(TService instance) => throw new NotImplementedException(); public void RegisterInstance(TService instance, string name) => throw new NotImplementedException(); public TService Resolve() => throw new NotImplementedException(); public TService Resolve(string name) => throw new NotImplementedException(); public bool TryResolve(out TService value, string name) => throw new NotImplementedException(); public bool TryResolve(out TService value) => throw new NotImplementedException(); public void Dispose() => IsDisposed = true; } } ``` - [ ] **Step 7: 运行测试确认入口测试通过** ```powershell dotnet build "My project/FlowScope.Runtime.csproj" --no-restore dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS build。此时只有 Root 入口行为成立。 **建议提交点:** `feat: 新增 ArchitectureScope 核心接口入口` ## Task 2: 实现 Register 只写当前 Scope **Files:** - Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs` - Create: `My project/Assets/FlowScope/Runtime/Core/Container/RegistrationBuilderAdapter.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs` - [ ] **Step 1: 增加 Register 测试** 向 `ArchitectureScopeRegistrationTests.cs` 增加: ```csharp [Test] public void Register_WritesOnlyCurrentScope() { using var root = FlowScope.FlowScope.Create(); using var child = root.CreateScope("Child"); root.Register().AsSingleton(); child.Register().AsSingleton(); Assert.That(root.Resolve(), Is.TypeOf()); Assert.That(child.Resolve(), Is.TypeOf()); } [Test] public void Register_DuplicateKeyInSameScopeThrows() { using var root = FlowScope.FlowScope.Create(); root.Register().AsSingleton(); Assert.Throws(() => root.Register().AsSingleton()); } private interface IService { } private sealed class RootService : IService { } private sealed class ChildService : IService { } ``` - [ ] **Step 2: 运行测试确认失败** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: FAIL,`CreateScope` / `Register` / `Resolve` 未实现。 - [ ] **Step 3: 用现有 Container 实现 Register 代理** `RegistrationBuilderAdapter.cs`: ```csharp using FlowScope.Container; namespace FlowScope.Core.Container { internal sealed class RegistrationBuilderAdapter : IRegistrationBuilder { private readonly ContainerRegistrationBuilder _inner; public RegistrationBuilderAdapter(ContainerRegistrationBuilder inner) { _inner = inner; } public void AsSingleton() => _inner.AsSingleton(); public void PerScope() => _inner.PerScope(); public void AsTransient() => _inner.AsTransient(); } } ``` 在 `ArchitectureScope` 中持有现有 Container: ```csharp private readonly FlowScope.Container.Container _container; public IRegistrationBuilder Register() { ThrowIfDisposed(); return new RegistrationBuilderAdapter(_container.Register()); } public IRegistrationBuilder Register(string name) { ThrowIfDisposed(); return new RegistrationBuilderAdapter(_container.Register(name)); } public IRegistrationBuilder Register() where TImplementation : TService { ThrowIfDisposed(); return new RegistrationBuilderAdapter(_container.Register()); } public IRegistrationBuilder Register(string name) where TImplementation : TService { ThrowIfDisposed(); return new RegistrationBuilderAdapter(_container.Register(name)); } public void RegisterInstance(TService instance) { RegisterInstance(instance, null); } public void RegisterInstance(TService instance, string name) { ThrowIfDisposed(); _container.RegisterInstance(instance, name); } ``` - [ ] **Step 4: 实现 Resolve 代理** ```csharp public TService Resolve() { ThrowIfDisposed(); return _container.Resolve(); } public TService Resolve(string name) { ThrowIfDisposed(); return _container.Resolve(name); } public bool TryResolve(out TService value) { ThrowIfDisposed(); return _container.TryResolve(out value); } public bool TryResolve(out TService value, string name) { ThrowIfDisposed(); return _container.TryResolve(out value, name); } ``` - [ ] **Step 5: 运行测试确认通过** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS build,新增 Register 测试通过。 **建议提交点:** `feat: 实现 ArchitectureScope 注册解析入口` ## Task 3: 实现 Scope Tree 与 Path **Files:** - Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs` - Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs` - [ ] **Step 1: 写 Scope Tree 测试** `ArchitectureScopeResolveTests.cs`: ```csharp using NUnit.Framework; namespace FlowScope.Tests.EditMode.Core { public sealed class ArchitectureScopeResolveTests { [Test] public void CreateScope_CreatesChildWithParentAndPath() { using var root = FlowScope.FlowScope.Create(); using var feature = root.CreateScope("BossFightFeature"); using var child = feature.CreateScope("RevivePanel"); Assert.That(feature.Parent, Is.SameAs(root)); Assert.That(child.Parent, Is.SameAs(feature)); Assert.That(feature.Path, Is.EqualTo("Root/BossFightFeature")); Assert.That(child.Path, Is.EqualTo("Root/BossFightFeature/RevivePanel")); } [Test] public void CreateScope_DuplicateSiblingNameThrows() { using var root = FlowScope.FlowScope.Create(); using var first = root.CreateScope("BossFightFeature"); Assert.Throws(() => root.CreateScope("BossFightFeature")); } } } ``` - [ ] **Step 2: 运行测试确认失败** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: FAIL,`CreateScope` 未实现或未检查重名。 - [ ] **Step 3: 在 ArchitectureScope 内维护子 Scope** 实现要点: ```csharp private readonly ArchitectureScope _parent; private readonly List _children = new List(); public IScopeInfo Parent => _parent; public IArchitectureScope CreateScope(string name) { ThrowIfDisposed(); if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Scope name cannot be empty.", nameof(name)); } if (_children.Exists(child => !child.IsDisposed && child.Name == name)) { throw new InvalidOperationException($"Scope '{name}' already exists under '{Path}'."); } var childContainer = _container.CreateScope(name); var child = new ArchitectureScope(name, this, childContainer); _children.Add(child); return child; } ``` 构造函数需要变成: ```csharp public ArchitectureScope(string name) : this(name, null, new FlowScope.Container.Container()) { } private ArchitectureScope(string name, ArchitectureScope parent, FlowScope.Container.Container container) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Scope name cannot be empty.", nameof(name)); } Name = name; _parent = parent; _container = container; Path = parent == null ? name : $"{parent.Path}/{name}"; } ``` - [ ] **Step 4: 确保 Dispose 从父节点移除** 在 `Dispose` 中释放子 Scope 后: ```csharp _parent?._children.Remove(this); ``` - [ ] **Step 5: 运行测试确认通过** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS build,Scope Tree / Path 测试通过。 **建议提交点:** `feat: 实现 ArchitectureScope 树结构` ## Task 4: 固化 Resolve 父级查找与子级覆盖 **Files:** - Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs` - Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs` - [ ] **Step 1: 增加父级查找与覆盖测试** ```csharp [Test] public void Resolve_FallsBackToParentWhenCurrentMissing() { using var root = FlowScope.FlowScope.Create(); using var feature = root.CreateScope("BossFightFeature"); var clock = new RootClock(42); root.RegisterInstance(clock); Assert.That(feature.Resolve(), Is.SameAs(clock)); } [Test] public void Resolve_ChildRegistrationOverridesParent() { using var root = FlowScope.FlowScope.Create(); using var feature = root.CreateScope("BossFightFeature"); root.Register().AsSingleton(); feature.Register().AsSingleton(); Assert.That(root.Resolve(), Is.TypeOf()); Assert.That(feature.Resolve(), Is.TypeOf()); } private interface IRootClock { int Value { get; } } private sealed class RootClock : IRootClock { public RootClock(int value) => Value = value; public int Value { get; } } private interface IRewardService { } private sealed class GlobalRewardService : IRewardService { } private sealed class BossRewardService : IRewardService { } ``` - [ ] **Step 2: 运行测试** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS。现有 `Container.TryFindRegistration` 已支持向父查找;如果失败,修复 Container 父子链传递。 - [ ] **Step 3: 增加 TryResolve 语义测试** ```csharp [Test] public void TryResolve_MissingRegistrationReturnsFalse() { using var root = FlowScope.FlowScope.Create(); Assert.That(root.TryResolve(out var value), Is.False); Assert.That(value, Is.Null); } ``` - [ ] **Step 4: 确保构造异常不被 TryResolve 吞掉** ```csharp [Test] public void TryResolve_ConstructorFailureThrows() { using var root = FlowScope.FlowScope.Create(); root.Register().AsTransient(); Assert.Throws(() => root.TryResolve(out _)); } private sealed class BrokenService { public BrokenService() { throw new InvalidOperationException("broken"); } } ``` Expected: PASS。若现有反射调用把异常包成 `TargetInvocationException`,按当前实际行为断言外层异常,但不要返回 false。 **建议提交点:** `test: 固化 Scope 解析查找语义` ## Task 5: 固化生命周期语义 **Files:** - Modify: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs` - Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeDisposeTests.cs` - [ ] **Step 1: 写生命周期测试** `ArchitectureScopeDisposeTests.cs`: ```csharp using NUnit.Framework; namespace FlowScope.Tests.EditMode.Core { public sealed class ArchitectureScopeDisposeTests { [Test] public void Singleton_FromParentIsSharedByChildrenAndDisposedByParent() { using var root = FlowScope.FlowScope.Create(); var first = root.CreateScope("First"); var second = root.CreateScope("Second"); root.Register().AsSingleton(); var rootInstance = root.Resolve(); Assert.That(first.Resolve(), Is.SameAs(rootInstance)); Assert.That(second.Resolve(), Is.SameAs(rootInstance)); first.Dispose(); Assert.That(rootInstance.IsDisposed, Is.False); root.Dispose(); Assert.That(rootInstance.IsDisposed, Is.True); } [Test] public void PerScope_FromParentCreatesDifferentInstancesPerChildScope() { using var root = FlowScope.FlowScope.Create(); var first = root.CreateScope("First"); var second = root.CreateScope("Second"); root.Register().PerScope(); var firstInstance = first.Resolve(); var secondInstance = second.Resolve(); Assert.That(first.Resolve(), Is.SameAs(firstInstance)); Assert.That(second.Resolve(), Is.SameAs(secondInstance)); Assert.That(firstInstance, Is.Not.SameAs(secondInstance)); first.Dispose(); Assert.That(firstInstance.IsDisposed, Is.True); Assert.That(secondInstance.IsDisposed, Is.False); } private interface IDisposableService { bool IsDisposed { get; } } private sealed class DisposableService : IDisposableService, System.IDisposable { public bool IsDisposed { get; private set; } public void Dispose() => IsDisposed = true; } } } ``` - [ ] **Step 2: 运行测试** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS。现有 Container 大概率已满足;如果失败,修复 `ContainerRegistration.ResolveScoped(requester)` 的缓存归属。 - [ ] **Step 3: 写 Dispose 顺序与幂等测试** ```csharp [Test] public void Dispose_ParentDisposesChildrenBeforeOwnInstancesAndIsIdempotent() { DisposeRecorder.Order.Clear(); var root = FlowScope.FlowScope.Create(); var child = root.CreateScope("Child"); root.Register().AsSingleton(); child.Register().AsSingleton(); root.Resolve(); child.Resolve(); root.Dispose(); root.Dispose(); CollectionAssert.AreEqual(new[] { "child", "root" }, DisposeRecorder.Order); } private interface ITracked { } private interface IChildTracked { } private static class DisposeRecorder { public static readonly System.Collections.Generic.List Order = new System.Collections.Generic.List(); } private sealed class RootTracked : ITracked, System.IDisposable { public void Dispose() => DisposeRecorder.Order.Add("root"); } private sealed class ChildTracked : IChildTracked, System.IDisposable { public void Dispose() => DisposeRecorder.Order.Add("child"); } ``` - [ ] **Step 4: 明确 Instance 不释放外部实例** 保留或迁移现有 `ContainerTests.RegisterInstance_Resolve_ReturnsSameInstanceAndDoesNotDisposeExternalInstance`,在 Core 测试中也覆盖: ```csharp [Test] public void RegisterInstance_DefaultDoesNotDisposeExternalInstance() { var root = FlowScope.FlowScope.Create(); var service = new DisposableService(); root.RegisterInstance(service); root.Dispose(); Assert.That(service.IsDisposed, Is.False); } ``` **建议提交点:** `test: 固化 ArchitectureScope 生命周期语义` ## Task 6: 实现 CreateFeature 自动创建与安装 **Files:** - Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs` - Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs` - [ ] **Step 1: 写 CreateFeature 测试** `ArchitectureScopeFeatureTests.cs`: ```csharp using System; using NUnit.Framework; namespace FlowScope.Tests.EditMode.Core { public sealed class ArchitectureScopeFeatureTests { [Test] public void CreateFeature_UsesFeatureTypeNameAndCallsInstall() { using var root = FlowScope.FlowScope.Create(); using var feature = root.CreateFeature(); Assert.That(feature.Name, Is.EqualTo(nameof(BossFightFeature))); Assert.That(feature.Path, Is.EqualTo("Root/BossFightFeature")); Assert.That(feature.Resolve(), Is.Not.Null); } [Test] public void CreateFeature_WithNameUsesExplicitName() { using var root = FlowScope.FlowScope.Create(); using var feature = root.CreateFeature("BossFight"); Assert.That(feature.Name, Is.EqualTo("BossFight")); Assert.That(feature.Resolve(), Is.Not.Null); } private sealed class BossFightFeature : IFeature { public void Install(IArchitectureScope scope) { scope.Register().PerScope(); } } private sealed class BossFightModel { } } } ``` - [ ] **Step 2: 实现 CreateFeature** 在 `ArchitectureScope` 中: ```csharp public IArchitectureScope CreateFeature() where TFeature : IFeature, new() { return CreateFeature(typeof(TFeature).Name); } public IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new() { ThrowIfDisposed(); var featureScope = (ArchitectureScope)CreateScope(name); try { var feature = new TFeature(); feature.Install(featureScope); return featureScope; } catch { featureScope.Dispose(); throw; } } ``` - [ ] **Step 3: 写 Install 失败清理测试** ```csharp [Test] public void CreateFeature_WhenInstallFailsDisposesCreatedScope() { using var root = FlowScope.FlowScope.Create(); Assert.Throws(() => root.CreateFeature()); using var feature = root.CreateFeature(nameof(BrokenFeature)); Assert.That(feature.Name, Is.EqualTo(nameof(BrokenFeature))); } private sealed class BrokenFeature : IFeature { public void Install(IArchitectureScope scope) { throw new InvalidOperationException("install failed"); } } ``` 这个测试证明失败 Scope 已从父级移除,否则同名创建会失败。 - [ ] **Step 4: 运行测试确认通过** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS build,CreateFeature 默认名、显式名、Install、失败清理全部通过。 **建议提交点:** `feat: 实现 CreateFeature 自动安装` ## Task 7: 迁移有价值的旧 Composition 用例 **Files:** - Modify: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs` - Modify: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs` - Delete or quarantine: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs` - [ ] **Step 1: 迁移 Root 服务可被 Feature Resolve** 新增测试: ```csharp [Test] public void FeatureScope_CanResolveRootServices() { using var root = FlowScope.FlowScope.Create(); var clock = new RootClock(10); root.RegisterInstance(clock); using var feature = root.CreateFeature(); Assert.That(feature.Resolve(), Is.SameAs(clock)); Assert.That(feature.Resolve().Value, Is.EqualTo(10)); } private interface IRootClock { int Value { get; } } private sealed class RootClock : IRootClock { public RootClock(int value) => Value = value; public int Value { get; } } private sealed class ClockFeature : IFeature { public void Install(IArchitectureScope scope) { scope.Register().PerScope(); } } private sealed class ClockModel { public ClockModel(IRootClock clock) => Value = clock.Value; public int Value { get; } } ``` - [ ] **Step 2: 迁移 Feature 级服务隔离** 新增测试: ```csharp [Test] public void TwoFeatureScopes_DoNotSharePerScopeFeatureServices() { using var root = FlowScope.FlowScope.Create(); root.Register().PerScope(); using var first = root.CreateFeature("FirstActivity"); using var second = root.CreateFeature("SecondActivity"); Assert.That(first.Resolve(), Is.SameAs(first.Resolve())); Assert.That(first.Resolve(), Is.Not.SameAs(second.Resolve())); } private interface IFeatureService { } private sealed class FeatureService : IFeatureService { } private sealed class EmptyFeature : IFeature { public void Install(IArchitectureScope scope) { } } ``` - [ ] **Step 3: 迁移命名注册** 新增测试: ```csharp [Test] public void NamedRegistration_ResolvesByTypeAndNameFromFeature() { using var root = FlowScope.FlowScope.Create(); root.Register("boss").PerScope(); root.Register("scratch").PerScope(); using var boss = root.CreateFeature("BossFight"); using var scratch = root.CreateFeature("ScratchTicket"); Assert.That(boss.Resolve("boss"), Is.TypeOf()); Assert.That(scratch.Resolve("scratch"), Is.TypeOf()); Assert.That(boss.Resolve("boss"), Is.SameAs(boss.Resolve("boss"))); Assert.That(boss.Resolve("boss"), Is.Not.SameAs(scratch.Resolve("boss"))); } private interface IRewardService { } private sealed class BossRewardService : IRewardService { } private sealed class ScratchRewardService : IRewardService { } ``` - [ ] **Step 4: 不迁移 Command / Query 测试** 旧测试 `FeatureScope_CommandAndQueryOperateOnScopedArchitecture` 不迁移。原因:spec 明确第一阶段不进入 Command / Query / Event。 - [ ] **Step 5: 运行测试** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: PASS build。 **建议提交点:** `test: 迁移 Composition 核心用例到 ArchitectureScope` ## Task 8: 删除或隔离旧 Composition 试验线 **Files:** - Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs` - Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs` - Delete or quarantine: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs` - [ ] **Step 1: 查找旧 Composition 引用** ```powershell rg -n "FlowScope\.Composition|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope" ``` Expected: 只剩 Runtime/Composition 与 Tests/EditMode/Composition 自身引用。 - [ ] **Step 2: 删除旧 Runtime/Composition 文件** 如果没有外部引用,执行: ```powershell git rm "My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs" "My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs.meta" "My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs" "My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs.meta" ``` 如果 Unity `.meta` 尚未生成或未跟踪,按实际文件列表只删除存在的路径。 - [ ] **Step 3: 删除旧 Composition 测试** ```powershell git rm "My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs" "My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs.meta" ``` - [ ] **Step 4: 删除空目录 meta** 如果目录为空,删除对应目录和 `.meta`: ```powershell git rm "My project/Assets/FlowScope/Runtime/Composition.meta" "My project/Assets/FlowScope/Tests/EditMode/Composition.meta" ``` 如果目录里仍有迁移期 adapter,不删除目录。 - [ ] **Step 5: 再次确认旧入口不存在** ```powershell rg -n "FlowScope\.Composition|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope" ``` Expected: exit code 1,表示没有匹配;这里 exit code 1 是通过条件。 **建议提交点:** `refactor: 移除旧 Composition 试验入口` ## Task 9: 总体验证与验收 **Files:** - Verify: `My project/FlowScope.Runtime.csproj` - Verify: `My project/FlowScope.Tests.EditMode.csproj` - Verify: `docs/specs/architecture-scope-container-implementation-spec.md` - [ ] **Step 1: 编译 Runtime** ```powershell dotnet build "My project/FlowScope.Runtime.csproj" --no-restore ``` Expected: Build succeeded。 - [ ] **Step 2: 编译 EditMode Tests** ```powershell dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore ``` Expected: Build succeeded。 - [ ] **Step 3: 验证负向边界** ```powershell rg -n "RootContext|FeatureScope|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope/Runtime" ``` Expected: exit code 1,表示 Runtime 中不再存在旧试验入口。 - [ ] **Step 4: 验证第一阶段没有引入后续模块** ```powershell rg -n "CommandContext|QueryContext|EventContext|UseMvvm|UseMvc|UIPanelLifecycle" "My project/Assets/FlowScope/Runtime/Core" ``` Expected: exit code 1,表示 Core 没有提前引入 Command / Query / Event / MVC / MVVM / UI Panel 生命周期接管。 - [ ] **Step 5: 格式与空白检查** ```powershell git diff --check ``` Expected: 无输出。 - [ ] **Step 6: 检查 staged 范围** 如果用户要求提交,执行者只应 stage 本计划相关文件: ```powershell git add "My project/Assets/FlowScope/Runtime/Core" "My project/Assets/FlowScope/Tests/EditMode/Core" "My project/Assets/FlowScope/Runtime/Container/Container.cs" "My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs" ``` 如果旧 Composition 文件被删除,再额外 stage 精确删除路径。不要 broad add 整个项目。 **建议最终提交信息:** `feat: 实现统一 ArchitectureScope 容器树` ## 自检清单 - [ ] `FlowScope.Create()` 返回 Root `IArchitectureScope`。 - [ ] Root / Feature / Child 都是同一种 `IArchitectureScope`。 - [ ] `CreateFeature()` 默认名为 `typeof(TFeature).Name`。 - [ ] `CreateFeature(name)` 自动创建 Scope 并调用 `Install`。 - [ ] 任意 Scope 可 Register,但只写自己。 - [ ] Resolve 从当前 Scope 开始,找不到向 Parent 查找。 - [ ] 子 Scope 覆盖父 Scope 时不修改父 Scope。 - [ ] Singleton 由注册所在 Scope 持有。 - [ ] PerScope 由 Resolve 发起 Scope 持有。 - [ ] Transient 每次创建新实例,并按最终规则释放。 - [ ] RegisterInstance 默认不释放外部实例。 - [ ] 子 Scope Dispose 不释放父 Scope 对象。 - [ ] 父 Scope Dispose 释放所有子 Scope。 - [ ] Dispose 幂等。 - [ ] 旧 `RootContext + FeatureScope + Command/Query` 不再是新核心入口。 - [ ] 第一阶段没有实现 Command / Query / Event、完整 MVC/MVVM、UI Panel 生命周期统一接管。