Deterministic Roslyn-based extractor for splitting C# files so each top-level type ends up in its own file.
This repository is also a Codex skill. The repository root is the skill root, so the recommended installation method is to clone it directly into your skills directory and update it later with git pull.
Recommended layout:
~/.codex/skills/extract-top-level-typeson macOS/Linux%USERPROFILE%\.codex\skills\extract-top-level-typeson Windows
Example:
git clone https://github.com/GbrosGames/extract-top-level-types.git $env:USERPROFILE\.codex\skills\extract-top-level-typesAfter cloning there, Codex can discover the skill directly because these files are at repository root:
SKILL.mdagents/openai.yamlscripts/
To update later:
git -C $env:USERPROFILE\.codex\skills\extract-top-level-types pullSKILL.md- skill instructions and workflow
agents/openai.yaml- UI metadata for the skill
scripts/extract-top-level-types.ps1- thin wrapper around
dotnet run
- thin wrapper around
scripts/extract-top-level-types-unicli.ps1- optional Unity wrapper for
unicliimport and compile flow
- optional Unity wrapper for
scripts/extract-top-level-types/Program.cs- extraction logic
scripts/extract-top-level-types/TopLevelTypeExtractor.csproj- tool project
For a given folder:
- scans all
.csfiles recursively - leaves files with
0or1top-level declaration unchanged - splits files with multiple top-level declarations into separate files
- keeps nested types inside their owner
- groups non-generic and generic variants with the same base name into one output
Example:
SomeClassSomeClass<T>
Both stay together in:
SomeClass.cs
- output file name is the top-level type name without generic arity
DiagnosticJobName<T>becomesDiagnosticJobName.cs- nested types do not get extracted on their own
The tool skips unsupported files instead of guessing:
partialtop-level declarations- parse-error files
- assembly/module attribute files
- batch output collisions
It preserves:
- namespace or global namespace shape
- attributes
- generic constraints
- preprocessor directives inside members and file-level wrappers such as
#if UNITY_EDITOR
- .NET 8 SDK
- PowerShell 7+
Optional for Unity workflow:
unicli- Unity Editor open with the target project loaded
From the repository root:
Dry run:
pwsh .\scripts\extract-top-level-types.ps1 C:\src\MyProject\Assets\Scripts\DiagnosticsApply:
pwsh .\scripts\extract-top-level-types.ps1 C:\src\MyProject\Assets\Scripts\Diagnostics -ApplyDirect dotnet invocation:
dotnet run --project .\scripts\extract-top-level-types\TopLevelTypeExtractor.csproj -- C:\src\MyProject\Assets\Scripts\Diagnostics
dotnet run --project .\scripts\extract-top-level-types\TopLevelTypeExtractor.csproj -- C:\src\MyProject\Assets\Scripts\Diagnostics --applyYou can also run it from inside the target repository with relative paths:
pwsh C:\tools\extract-top-level-types\scripts\extract-top-level-types.ps1 .\Assets\Scripts\Diagnostics
pwsh C:\tools\extract-top-level-types\scripts\extract-top-level-types.ps1 .\Assets\Scripts\Diagnostics -Applyscripts/extract-top-level-types-unicli.ps1 is optional. It keeps the extractor generic and adds Unity-specific orchestration:
- runs the extractor on one folder
- imports new and modified
.csassets throughunicli - reimports the folder when files were deleted
- runs
unicli exec Compile --json - retries compile once with
UnityEditor.SyncVS.SyncSolution()when deleted files leave stale project entries behind
Usage from the Unity project root:
pwsh C:\tools\extract-top-level-types\scripts\extract-top-level-types-unicli.ps1 Assets/Scripts/Diagnostics
pwsh C:\tools\extract-top-level-types\scripts\extract-top-level-types-unicli.ps1 Assets/Scripts/Diagnostics -ApplyUsage from anywhere with an explicit project root:
pwsh .\scripts\extract-top-level-types-unicli.ps1 Assets/Scripts/Diagnostics -ProjectRoot C:\src\MyUnityProject
pwsh .\scripts\extract-top-level-types-unicli.ps1 Assets/Scripts/Diagnostics -ProjectRoot C:\src\MyUnityProject -ApplyThe wrapper has no hardcoded machine-specific project paths.
If the target project is a git repository, the wrapper refuses to run -Apply on a folder that already has uncommitted changes.
Dry-run output:
PLAN ...- file will be rewritten or split
SKIP ...- file was intentionally not touched
SUMMARY ...- final counts
Apply output:
WRITE ...- file written
DELETE ...- source file removed because all declarations moved elsewhere
Commiting is intentionally left outside the tool:
- import and compile steps are project-specific
- Unity projects need
.metageneration before a clean commit - different repositories may want different commit granularity
- some repositories should not auto-commit from a refactor helper
- no semantic analysis across projects or solutions
- no automatic handling of
partialtop-level declarations - no automatic git workflow
- the Unity wrapper depends on
unicliand a running Unity Editor
- run folder by folder
- prefer dry-run first
- compile after each applied folder in Unity projects
- commit folder by folder in the target repository