uw-dependency-injection-499eee — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited uw-dependency-injection-499eee (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
DI-first architecture for complex Unity projects. Only applies when `ProjectConfig.yaml -> architecture_pattern: di-first`. For simpler projects, use uw-scriptable-object-arch (SO-first) instead.
docs/ProjectConfig.yaml for:architecture_pattern — must be "di-first" for this skill to apply.di_framework — should be "reflex" (the default and recommended framework).mcp.unity_mcp — if true, call refresh_unity after creating files.docs/CODING_STANDARDS.md for async patterns (Awaitable + CancellationToken), access modifiers (internal for cross-class helpers within an asmdef), and class structure.docs/NAMING_CONVENTIONS.md for file/class naming..asmdef — create with uw-unity-feature-scaffold. Installers, Controllers, Services all live inside feature asmdefs.| SO-First (default) | DI-First |
|---|---|
| Small/medium scope | Large/complex scope |
| Solo or small team | Team with testing culture |
| Rapid prototyping | Production architecture |
| Simple event-driven communication | Cross-feature orchestration via Commands |
| ScriptableObject event channels | Interface-based injection + factories |
Both can coexist: DI containers can bind ScriptableObject data assets as singletons.
Install (OpenUPM): openupm install com.gustavopsantos.reflex Or UPM Git URL: https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/
RootScope (app lifetime — singletons, core services)
+-- SceneScope (scene lifetime — per-scene services, controllers)
+-- Manual child scopes (gameplay round, level, etc.)Each feature module has one Installer that registers its bindings. The Installer lives inside the feature's .asmdef.
using Reflex.Core;
using UnityEngine;
namespace {RootNamespace}.Core
{
public class CoreInstaller : MonoBehaviour, IInstaller
{
[SerializeField] private AudioSettingsData _audioSettings;
public void InstallBindings(ContainerBuilder builder)
{
// Services (singleton, app lifetime)
builder.AddSingleton(typeof(IAudioService), typeof(AudioService));
builder.AddSingleton(typeof(IStateMachineService), typeof(StateMachineService));
// SO data (inject existing asset as value)
builder.AddInstance(_audioSettings);
}
}
}| Method | What It Does | Lifetime |
|---|---|---|
AddSingleton(type, impl) | Container creates one instance | App/Scene |
AddTransient(type, impl) | New instance each resolve | Per-resolve |
AddInstance(obj) | Register existing object (SO, config) | Singleton |
using Reflex.Attributes;
namespace {RootNamespace}.Combat
{
public class ArrowController
{
[Inject] private readonly IAudioService _audio;
[Inject] private readonly IArrowMovementController _movement;
// OR constructor injection (preferred for non-MonoBehaviours):
public ArrowController(IAudioService audio, IArrowMovementController movement)
{
_audio = audio;
_movement = movement;
}
}
}Constructor injection is preferred for plain C# classes — it makes dependencies explicit and prevents forgetting to inject. Use [Inject] attribute injection for MonoBehaviours (which Unity constructs).
Always bind to interfaces, not concrete types. This enables mock injection for testing, makes dependencies explicit, and follows the Dependency Inversion Principle.
// Correct — bind to interface
builder.AddSingleton(typeof(IAudioService), typeof(AudioService));
// Wrong — binding concrete directly prevents mock injection
builder.AddSingleton(typeof(AudioService));Circular dependencies (A depends on B, B depends on A) cause infinite resolution loops. Prevent them with:
uw-scriptable-object-arch event channels.{RootNamespace}.Core.asmdef so both features depend on the interface, not on each other.If the container throws a circular dependency error, trace the dependency chain and break it at the point where a Command or event channel makes more sense than a direct reference.
Only one scene has a Start() method. All other initialization flows from there.
CoreScene loads -> RootScope binds -> CoreInitiator.Start()
-> Load GameScene (additively) -> SceneScope binds -> GameInitiator.Init()
-> Load GamePlayScene -> SceneScope binds -> GamePlayInitiator.Init()Rules:
Start() method — everything else is initialized via async InitEntryPoint().See references/class-taxonomy-and-commands.md for the full 9-class taxonomy, Command pattern, and centralized Update management.
Key rule: Commands are the ONLY classes allowed to cross feature boundaries. This prevents circular dependencies — each Command resolves its own references from the DI container at execution time.
When Feature A (Combat) needs to notify Feature B (Score) about a hit:
// Lives in a shared Commands assembly or in the feature that initiates it
public class OnHitScoredCommand : ICommand
{
[Inject] private readonly IScoreController _score;
[Inject] private readonly IAudioService _audio;
public void Execute()
{
_score.AddPoints(10);
_audio.PlayOneShot(AudioClipType.Hit);
}
}The Combat feature doesn't reference Score directly — it resolves and executes the Command, which the container wires up. Register in the installer: builder.AddTransient(typeof(OnHitScoredCommand));
DI and SO work together — SO holds data, DI manages services and wiring:
// In installer: inject SO data asset into container
[SerializeField] private WeaponDatabase _weaponDatabase;
public void InstallBindings(ContainerBuilder builder)
{
builder.AddInstance(_weaponDatabase); // SO data available via [Inject]
}See uw-scriptable-object-arch for data container and event channel patterns that complement DI.
uw-unity-feature-scaffold to create feature modules with their own .asmdef — each feature gets its own Installer.uw-state-machine for game flow states, with states receiving dependencies via constructor injection.uw-unity-test-runner — interface-based DI makes testing easy: construct classes with mock dependencies, no container needed in tests.uw-scriptable-object-arch for game data assets that get injected into services via AddInstance.ProjectConfig.yaml -> architecture_pattern: di-first.[Inject] attribute for MonoBehaviours..asmdef (per NAMING_CONVENTIONS.md). Installers live inside the feature's asmdef.[SerializeField] private for Inspector-exposed fields on Installers and MonoBehaviours — never public fields.Awaitable with CancellationToken for async operations (per CODING_STANDARDS.md).Start().ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.