Status (2026-10-03): this is the original design spec, kept for its history. The compile pipeline was redesigned to fix issues #1–#12: every
.spyis compiled as onesharpyc project, staged inLibrary/Sharpy/and synced into the generated folder; diagnostics come from the compile's own output;#linedirectives are rewritten by the plugin; generated scripts get class-named files and GUIDs derived from the.spy; references are derived from Unity. Parts this changed are marked [SUPERSEDED] below. The current design is inCLAUDE.md(Key Design Decisions, Architecture) andDocumentation~/index.md(How Compilation Works).
Sharpy compiles .spy source files to C# via sharpyc emit csharp, which Unity can then compile normally. The goal is a Unity Editor plugin that makes .spy files "just work" inside a Unity project — edit a .spy file, Unity detects the change, runs the Sharpy compiler, and the generated C# is picked up by Unity's normal compilation pipeline.
Repo name recommendation: sharpy-unity — follows Unity package naming conventions (lowercase-hyphenated). The Unity package itself would be com.antonsynd.sharpy (UPM convention). This should be a separate repo because:
- Different release cadence from the compiler
- Unity-specific CI (Unity Test Runner, specific Unity versions)
- Users install it via Unity Package Manager, not NuGet
- Keeps the compiler repo focused
sharpyc emit csharp <file.spy> -o <output.cs>works for single files (exit 0 = success, exit 1 = errors)sharpyc emit diagnostics <file.spy> --format jsonoutputs structured diagnostics as JSONsharpyc project <.spyproj> --emit-cs-to <dir>compiles multi-file projectsSharpy.Coremulti-targetsnet10.0;netstandard2.1— thenetstandard2.1build is Unity-compatible- Generated C# uses
global::Sharpy.Builtins.*,Sharpy.List<T>, etc. — requiresSharpy.Core.dllat runtime - No Unity integration exists today
-
Transpile-then-compile, not custom compiler — Unity's compilation pipeline is opaque and version-sensitive. Generating C# and letting Unity compile it is far more robust than trying to inject IL or replace the compiler. This also means Sharpy diagnostics appear at edit-time, not buried in Unity's compile errors.
-
AssetPostprocessoroverScriptedImporter—ScriptedImporteris designed for non-code assets (models, data). It producesUnityEngine.Objectassets, not source files that feed into compilation.AssetPostprocessorfires on any file import and can trigger C# regeneration +AssetDatabase.Refresh(). -
Generated files in
Assets/SharpyGenerated/— a dedicated directory keeps generated C# separate from hand-written code. A.gitignoreentry prevents committing generated files. The directory mirrors the source structure (e.g.,Assets/Scripts/player.spy→Assets/SharpyGenerated/Scripts/player.cs). -
Ship
Sharpy.Core.dllas a plugin — thenetstandard2.1build ofSharpy.Coredrops intoAssets/Plugins/Sharpy.Core/. This is a runtime dependency (not editor-only). -
Ship
sharpycas an editor-only binary — the compiler CLI is bundled inEditor/Binaries/(platform-specific: macOS, Windows, Linux). Editor-only so it's excluded from builds. [SUPERSEDED:sharpycis not bundled.SharpyBinaryDownloaderdownloads the pinned release per platform into the project'sLibrary/SharpyCompiler/<version>/<rid>/; the archive holds ~350 DLLs Unity would try to import from a package folder.] -
Namespace wrapping for Unity — generated C# from single-file
emit csharphas no namespace (multi-fileprojectcompilation already wraps inProjectNamespacefrom.spyproj). The plugin should pass a configurable namespace to avoid global scope pollution in Unity projects. This requires a new--namespaceflag onsharpyc emit csharpfor single-file mode (upstream change insharpyrepo). [CORRECTED: multi-file projects already support namespace wrapping via_context.ProjectNamespacefrom.spyprojconfig; only single-fileemit csharplacks this] [SUPERSEDED: the plugin uses project mode only, so namespaces come from the generated.spyproj(RootNamespace, defaultSharpyScripts, plus the folder path from<SourceRoot>, i.e.Assets/). No--namespaceflag is needed.]
Goal: Set up the sharpy-unity repo as a valid Unity Package Manager (UPM) package.
-
Create repo structure —
sharpy-unity/package.json(UPM manifest:com.antonsynd.sharpy, version, dependencies, Unity version min)README.mdLICENSECHANGELOG.mdEditor/(editor-only scripts, compiler binaries)Runtime/(runtime helpers if needed, Sharpy.Core.dll lives here or in Plugins)Plugins/Sharpy.Core/(netstandard2.1 DLL + meta files)Tests/Editor/(Unity Test Runner tests).gitignore- Acceptance criteria:
sharpy-unitycan be installed via UPM from git URL - Commit:
chore: scaffold sharpy-unity UPM package
-
Add Sharpy.Core.dll —
Plugins/Sharpy.Core/- Build
Sharpy.Corefornetstandard2.1and copy the DLL - Add
.metafiles (Unity requires them) - Verify it loads in a test Unity project without errors
- Add
System.Collections.Immutable.dll(Sharpy.Core dependency) - Add
Microsoft.Bcl.AsyncInterfaces.dll(Sharpy.Core netstandard2.1 dependency) [CORRECTED: Sharpy.Core.csproj also references Microsoft.Bcl.AsyncInterfaces for netstandard2.1] - Acceptance criteria: A Unity script can
using Sharpy;and callBuiltins.Print() - Commit:
feat: bundle Sharpy.Core.dll for Unity runtime
- Build
-
Add sharpyc binaries —
Editor/Binaries/- Publish self-contained
sharpycfor macOS (arm64, x64), Windows (x64), Linux (x64) - Add platform-specific
.metafiles with correct platform filters - Mark as Editor-only (exclude from player builds)
- Acceptance criteria:
SharpyCompilerBridgecan locate and invoke the correct binary for the current OS - Commit:
feat: bundle sharpyc compiler binaries (macOS/Windows/Linux)
- Publish self-contained
Goal: A C# editor class that invokes sharpyc and parses results.
-
SharpyCompilerBridge.cs—Editor/SharpyCompilerBridge.cs- Locate the correct platform-specific
sharpycbinary - [SUPERSEDED:
CompileFile,GetDiagnosticsand the JSON parser were removed. Per-file emit wrote imported modules next to each importer and gave the same module different namespaces (#4), andemit diagnosticsaccepts no references (#3). The bridge now only runssharpyc project;SharpyProjectCompilerdrives it andSharpyDiagnosticParserreads the rendered diagnostics. Exit codes: 0 success, 1 Sharpy errors, 2 generated C# does not compile, 3 internal error.] CompileFile(string spyPath, string outputCsPath)— runssharpyc emit csharp <spy> -o <cs>, returns structured resultCompileProject(string spyprojPath, string outputDir)— runssharpyc project <spyproj> --emit-cs-to <dir>GetDiagnostics(string spyPath)— runssharpyc emit diagnostics <spy> --format json, parses JSON- Capture stdout, stderr, exit code
- Parse JSON diagnostics into
SharpyDiagnosticrecords (severity,code,line,column,message) - Timeout handling (kill process after configurable duration, default 30s)
- Acceptance criteria: Unit test calls
CompileFile()with a valid.spyand gets generated C# at the output path; invalid.spyreturns diagnostics with correct line/column - Commit:
feat: add SharpyCompilerBridge for invoking sharpyc from editor
- Locate the correct platform-specific
-
SharpyDiagnostic.cs—Editor/SharpyDiagnostic.cs- Data class:
Severity(Error/Warning/Info),Code(string, e.g. "SPY0200"),Line,Column,Message,FilePath,Phase[CORRECTED: the JSON output fromemit diagnostics --format jsonincludes aphasefield (e.g., "Lexer", "Parser", "TypeChecking") that should be captured;filePathis NOT in the JSON for single-file, only inferred from context] ToUnityLogType()helper for mapping toLogType.Error/Warning/Log- Acceptance criteria: Diagnostics parse correctly from JSON output
- Commit:
feat: add SharpyDiagnostic model for structured compiler output
- Data class:
Goal: Detect .spy file changes and auto-compile to C#.
-
SharpyAssetPostprocessor.cs—Editor/SharpyAssetPostprocessor.cs- Subclass
AssetPostprocessor, overrideOnPostprocessAllAssets() - Filter for
.spyfile imports/moves/deletes - [SUPERSEDED: any imported, moved or deleted
.spytriggers oneSharpyProjectCompiler.Compile()of the whole project; generated files are synced fromLibrary/Sharpy/emitonly after a successful build. The in-memory hash check became a persisted fingerprint (Library/Sharpy/fingerprint), also checked on editor load and focus.] - On create/modify: invoke
SharpyCompilerBridge.CompileFile(), write.csto mirror path underAssets/SharpyGenerated/ - On delete: delete corresponding generated
.csfile - On move: delete old
.cs, compile to new mirror path - Log Sharpy diagnostics to Unity Console with correct severity and double-click-to-open support (using
Debug.LogErrorwith context object pointing to the.spyfile) - Skip regeneration if
.spyfile hasn't changed (hash check) - Acceptance criteria: Save a
.spyfile in Unity → generated.csappears inSharpyGenerated/→ Unity compiles it; delete the.spy→ generated.csis removed - Commit:
feat: auto-compile .spy files on import via AssetPostprocessor
- Subclass
-
SharpyGeneratedFolderManager.cs—Editor/SharpyGeneratedFolderManager.cs- Ensure
Assets/SharpyGenerated/exists with a.gitignorecontaining*(don't version generated files) - Create directory structure mirroring source
.spylocations - Add
[InitializeOnLoad]setup to create the folder on first load - Clean up empty subdirectories when
.spyfiles are deleted - Acceptance criteria: Generated folder is created automatically, never committed to git
- Commit:
feat: manage SharpyGenerated output directory lifecycle
- Ensure
Goal: Settings, manual controls, and diagnostic display.
-
SharpySettings.cs—Editor/SharpySettings.csScriptableSingleton<SharpySettings>stored inProjectSettings/- Settings:
generatedOutputPath(defaultAssets/SharpyGenerated),compilerTimeoutSeconds(default 30),autoCompileOnSave(default true),rootNamespace(default empty),showLineDirectives(default false),additionalModulePaths(list),additionalReferences(list) - [SUPERSEDED:
showLineDirectiveswas removed and replaced bysourceMappedErrors(default true). Added:customCompilerPath,autoUnityReferences,referenceDenylist.compilerTimeoutSecondsnow bounds a whole-project compile. Settings edited on disk are reloaded (#12).] - Acceptance criteria: Settings persist across Unity sessions
- Commit:
feat: add SharpySettings for project-level configuration
-
SharpySettingsProvider.cs—Editor/SharpySettingsProvider.csSettingsProviderforProject/Sharpyin Unity's Preferences/Project Settings window- UI for all settings from
SharpySettings - "Recompile All" button that forces regeneration of all
.spyfiles - "Locate Compiler" field showing detected
sharpycpath and version - Acceptance criteria: Settings appear in Project Settings → Sharpy, changes take effect immediately
- Commit:
feat: add Sharpy settings UI in Project Settings window
-
SharpyMenuItems.cs—Editor/SharpyMenuItems.csAssets/Sharpy/Recompile All— force-recompile every.spyin the projectAssets/Sharpy/Recompile Selected— recompile selected.spyfile(s) [SUPERSEDED: removed; a single file cannot be compiled on its own in project mode. Clean Generated also clearsLibrary/Sharpy/.]Assets/Sharpy/Clean Generated— delete all generated.csfiles- Right-click context menu on
.spyfiles: "View Generated C#" (opens the generated.csin the code editor) - Acceptance criteria: Menu items work and show progress bar for bulk operations
- Commit:
feat: add Sharpy editor menu items for manual compilation control
-
.spyfile icon and inspector —Editor/SharpyFileInspector.cs- Custom icon for
.spyfiles in the Project window (simple snake/python-inspired icon) - Custom inspector when a
.spyfile is selected: shows file info, last compile status, diagnostics summary, "Recompile" button - Acceptance criteria:
.spyfiles have a distinct icon; selecting one shows compile status - Commit:
feat: add custom icon and inspector for .spy files
- Custom icon for
Goal: When Unity shows a compiler error in generated C#, clicking it should navigate to the .spy source.
-
Enable
#linedirectives by default —Editor/SharpyCompilerBridge.cs- [SUPERSEDED: project mode always emits
#linedirectives, in both the classic and the C# 10 span form, which Unity's C# 9 rejects.SharpyLineDirectivesrewrites span directives to the classic form during the sync, keeps paths absolute (Unity resolves a relative#linepath against the generated.cs's folder), and strips all directives whensourceMappedErrorsis off. No upstream flag is used.] - Pass
--show-line-directivestosharpyc emit csharpso generated C# contains#line N "file.spy"directives - Unity's C# compiler respects
#line— errors will reference the.spyfile and line number - When user double-clicks an error, Unity opens the
.spyfile at the correct line (if an external editor is configured for.spy) - Acceptance criteria: A type error in generated C# shows the
.spyfilename and line in Unity Console; double-click opens the.spysource - Commit:
feat: enable #line directives for source-mapped error navigation
- [SUPERSEDED: project mode always emits
-
File association for
.spy—Editor/SharpyFileHandler.cs- Register
.spyas a text file type in Unity so double-clicking opens it in the configured external editor (VS Code, Rider, etc.) - Use
[OnOpenAsset]callback to handle.spyfile opens - Acceptance criteria: Double-clicking a
.spyfile in the Project window opens it in the external code editor - Commit:
feat: register .spy files for external editor opening
- Register
Goal: Add features to the Sharpy compiler that improve Unity integration.
-
--namespaceflag foremit csharp—src/Sharpy.Cli/andsrc/Sharpy.Compiler/[SUPERSEDED: not needed; see design decision 6. The upstream change the plugin does need is a<SourceRoot>spyproj property, so the module root stays atAssets/.]- Add
--namespace <ns>option toemit csharpcommand - When provided, wrap generated classes in
namespace <ns> { ... } - Default: no namespace (current behavior, backwards-compatible)
- Acceptance criteria:
sharpyc emit csharp --namespace Game.Scripts file.spyproduces C# wrapped innamespace Game.Scripts { ... } - Commit:
feat(cli): add --namespace flag for generated C# namespace wrapping
- Add
-
--suppress-entry-pointflag —src/Sharpy.Cli/andsrc/Sharpy.Compiler/- Note:
emit csharpalready has--type library(-t library) which setsOutputType = "library"and suppressesMain()generation. Consider whether this existing flag is sufficient for Unity's needs, or if a separate--suppress-entry-pointis still needed for finer control (e.g., exe-mode compilation without aMain()method). [CORRECTED:--type libraryalready exists onemit csharpand suppresses entry point requirements viaCompilerOptions.OutputType] - Module-level code (outside functions/classes) could be emitted as an
[InitializeOnLoadMethod]or[RuntimeInitializeOnLoadMethod]annotated method instead — or just warned against - Acceptance criteria:
sharpyc emit csharp --type library file.spyproduces C# withoutstatic void Main()(already works); evaluate if additional flag is needed - Commit:
feat(cli): add --suppress-entry-point flag for library-mode emission(if needed beyond--type library)
- Note:
Goal: Docs, sample project, and CI for the Unity package.
-
Sample Unity project —
Samples~/BasicSetup/- UPM samples directory with a minimal Unity project
- A few
.spyfiles demonstrating: a MonoBehaviour written in Sharpy, using Sharpy stdlib in Unity, multi-file imports README.mdwith setup instructions- Acceptance criteria: User can import the sample from Package Manager and see it work
- Commit:
docs: add sample Unity project with Sharpy integration
-
Documentation —
Documentation~/- Installation guide (UPM from git URL, from disk, from registry)
- Configuration guide (settings, namespace, module paths)
- Workflow guide (editing
.spyfiles, viewing generated C#, debugging) - Limitations and known issues
- Acceptance criteria: A new user can follow the docs to set up Sharpy in a Unity project
- Commit:
docs: add installation, configuration, and workflow guides
-
CI workflow —
.github/workflows/unity-ci.yml- Use GameCI or Unity Builder action
- Test against Unity 2022 LTS + Unity 6
- Run Unity Test Runner for editor tests
- Validate that sample project compiles
- Acceptance criteria: CI passes on PR; tests run on supported Unity versions
- Commit:
ci: add Unity CI workflow with GameCI
SharpyCompilerBridgeTests: invoke compiler with valid/invalid.spy, verify results and diagnosticsSharpyDiagnosticTests: parse JSON diagnostics, verify severity/code/line mappingSharpyGeneratedFolderManagerTests: create/delete/move file mirroringSharpySettingsTests: settings persistence and defaults
- End-to-end: create
.spyfile → verify.csgenerated → verify Unity compiles it - Delete
.spy→ verify.cscleaned up - Modify
.spywith error → verify diagnostics logged, no.cswritten - Multi-file project: imports between
.spyfiles resolve correctly #linedirectives: verify error points to.spysource line
.spyfile with syntax errors (should show diagnostics, not crash).spyfile importing another.spyfile (multi-file compilation)- Renaming/moving
.spyfiles (old generated files cleaned up) - Large project (100+
.spyfiles) — performance of bulk recompilation - Platform-specific: verify correct
sharpycbinary selected on macOS/Windows/Linux - Unity domain reload: verify state survives assembly reload
.spyfile outsideAssets/(should be ignored)
- Missing
sharpycbinary → clear error in Console Sharpy.Core.dllmissing → clear error at compile time- Circular imports between
.spyfiles → compiler error surfaces correctly .spyfile with same name as existing.csfile → warning about name collision
These changes should be implemented in the sharpy repo before or alongside the Unity plugin:
- Phase 6, Task 14:
--namespaceflag (new CLI option + codegen support) - Phase 6, Task 15:
--suppress-entry-pointflag — may be unnecessary if--type librarysuffices (already exists) - Sharpy.Core NuGet/DLL publishing: Automated build that produces standalone
netstandard2.1DLL artifacts for bundling
sharpy-unity — matches the pattern of other Unity integration packages (e.g., firebase-unity, steamworks-unity). The UPM package name would be com.antonsynd.sharpy.
No existing GitHub issues — this is a new initiative. Consider creating:
sharpyrepo: Issue for--namespaceflagsharpyrepo: Issue for--suppress-entry-pointflagsharpy-unityrepo: Tracking issue for the full integration
Result: PASS WITH CORRECTIONS Verified on: 2026-04-30 Plan file: ~/.claude/plans/plan-8ae0d3.md
-
Task 2 — Missing dependency: Added
Microsoft.Bcl.AsyncInterfaces.dllto the list of DLLs to bundle.Sharpy.Core.csprojreferences bothSystem.Collections.ImmutableandMicrosoft.Bcl.AsyncInterfacesfor thenetstandard2.1target. -
Task 5 — SharpyDiagnostic model incomplete: The JSON output from
emit diagnostics --format jsonincludes aphasefield (values like"Lexer","Parser","TypeChecking", etc.) that was missing from the proposed data class. Also noted thatfilePathis not present in the JSON output for single-file compilation — it would need to be supplied by the caller. -
Design Decision 6 — Namespace claim corrected: The plan stated "generated C# currently has no namespace" — this is only true for single-file
emit csharp. Multi-file projects (sharpyc project) already wrap generated C# in theProjectNamespacefrom the.spyprojfile viaRoslynEmitter.CompilationUnit.cs. -
Task 15 —
--suppress-entry-pointpartially redundant:emit csharpalready has--type library(-t library) which setsCompilerOptions.OutputType = "library"and suppressesMain()generation. The plan should evaluate whether this existing flag suffices for Unity or if a separate flag adds value.
-
NuGet transitive dependencies:
System.Collections.Immutableversion 10.0.7 may itself pull transitive dependencies. When bundling for Unity, consider usingdotnet publishto collect all required DLLs rather than manually listing them. -
emit csharpwrites to file, not stdout: TheEmitCSharpfunction always writes the generated C# to a file (either-otarget or default.csreplacement).SharpyCompilerBridge.CompileFile()should capture the output file path from theConsole.WriteLinemessage, or better, just specify-oexplicitly. -
Unity version compatibility: The plan mentions Unity 2022 LTS + Unity 6 for CI but doesn't specify minimum Unity version for the package itself.
netstandard2.1is supported from Unity 2021.2+. Consider documenting this.
-
Sharpy.Core transitive NuGet closure: Before Phase 1 Task 2, run
dotnet publish src/Sharpy.Core -f netstandard2.1 -c Releaseand inspect the output to identify ALL required DLLs (not just the two listed). This ensures nothing is missed. -
sharpycself-contained publish: Phase 1 Task 3 mentions "publish self-contained sharpyc" but doesn't specify the TFM/RID matrix or the publish command. Use:dotnet publish src/Sharpy.Cli -c Release --self-contained -r <rid>for each target RID (osx-arm64, osx-x64, win-x64, linux-x64).
-
Unity
AssetPostprocessor.OnPostprocessAllAssets()behavior: The plan assumes this callback fires on.spyfile saves. Unity only imports files it recognizes as assets;.spyis an unknown extension and may require aScriptedImporterregistration just to trigger import callbacks. Could not verify without a Unity project — recommend testing early in Phase 3. -
#linedirectives in Unity: The plan assumes Unity's C# compiler respects#linefor error navigation. This is likely true (Roslyn-based compilers do), but Unity's exact compilation pipeline varies by version. Recommend verifying in target Unity versions.