using System; using System.Collections.Generic; using System.IO; using System.Text; using UnityEditor; using UnityEngine; namespace RunlevelSystems.DevPlus1.EditorTools { [Serializable] public class RLProjectAnalysisData { public string projectName; public string unityVersion; public string generatedAt; public List scenes = new List(); public List scripts = new List(); public List prefabs = new List(); public RLGitReportSummary gitReports = new RLGitReportSummary(); public List suggestedFirstFiles = new List(); public List risksOrUnknowns = new List(); } [Serializable] public class RLSceneInfo { public string scenePath; public string sceneName; public int buildIndex = -1; public int rootGameObjectCount; public int totalGameObjects; public int activeGameObjects; public int inactiveGameObjects; public int missingScriptCount; public int missingReferenceCount; public List rootGameObjects = new List(); public List gameObjects = new List(); public List unityEvents = new List(); public string notes; } [Serializable] public class RLGameObjectInfo { public string hierarchyPath; public bool activeSelf; public string tag; public int layer; public List components = new List(); public List attachedScripts = new List(); public List referencedObjects = new List(); public int missingScriptCount; public int missingReferenceCount; } [Serializable] public class RLScriptInfo { public string scriptPath; public string className; public string baseClass; public bool isMonoBehaviour; public bool isScriptableObject; public List lifecycleMethods = new List(); public List publicMethods = new List(); public List serializedFields = new List(); public List publicFields = new List(); public List unityApiUsage = new List(); public string possibleRole; } [Serializable] public class RLPrefabInfo { public string prefabPath; public List components = new List(); public List attachedScripts = new List(); public List nestedPrefabs = new List(); public List importantReferences = new List(); public int missingScriptCount; public int missingReferenceCount; } [Serializable] public class RLUnityEventInfo { public string sourcePath; public string gameObjectPath; public string componentName; public string eventName; public string targetObject; public string targetComponent; public string targetMethod; } [Serializable] public class RLGitReportSummary { public bool reportsFolderFound; public bool validationReportFound; public bool validationFoundErrors; public int validationWarnings = -1; public int validationErrors = -1; public bool apiReferenceFound; public bool filePurposeFound; public bool staticCallMapFound; public bool unityScriptReportFound; public List likelyEntryPoints = new List(); public List importantFiles = new List(); public List foundReports = new List(); public List missingReports = new List(); public string notes; } public static class RLReportExporter { public const string RootFolder = "Assets/RL_DevPlus1"; public const string ReportFolder = RootFolder + "/Reports"; public static void EnsureFolders() { EnsureAssetFolder("Assets", "RL_DevPlus1"); EnsureAssetFolder(RootFolder, "Reports"); EnsureAssetFolder(RootFolder, "Docs"); EnsureAssetFolder(RootFolder, "Runtime"); } public static string AbsoluteReportFolder { get { return Path.GetFullPath(Path.Combine(Application.dataPath, "RL_DevPlus1/Reports")); } } public static void ExportAll(RLProjectAnalysisData data) { EnsureFolders(); WriteProjectSummary(data); WriteSceneMap(data); WriteGameObjectMap(data); WriteScriptMap(data); WriteEventMap(data); WritePrefabMap(data); WriteExecutionOrder(data); WriteMissingReferences(data); WriteGitSummary(data.gitReports); WriteProjectDataJson(data); WriteAIContext(data); WriteReportIndex(); AssetDatabase.Refresh(); } public static void WriteGitSummary(RLGitReportSummary git) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Git Report Summary"); sb.AppendLine(); sb.AppendLine("- Git reports folder found: " + BoolText(git.reportsFolderFound)); sb.AppendLine("- Validation report found: " + BoolText(git.validationReportFound)); sb.AppendLine("- Validation found errors: " + BoolText(git.validationFoundErrors)); sb.AppendLine("- Validation warnings: " + ValueOrUnknown(git.validationWarnings)); sb.AppendLine("- Validation errors: " + ValueOrUnknown(git.validationErrors)); sb.AppendLine("- API reference found: " + BoolText(git.apiReferenceFound)); sb.AppendLine("- File purpose report found: " + BoolText(git.filePurposeFound)); sb.AppendLine("- Static call map found: " + BoolText(git.staticCallMapFound)); sb.AppendLine("- Unity script report found: " + BoolText(git.unityScriptReportFound)); AppendList(sb, "Likely Entry Points From Git Reports", git.likelyEntryPoints); AppendList(sb, "Important Files From Git Reports", git.importantFiles); AppendList(sb, "Found Reports", git.foundReports); AppendList(sb, "Missing Reports", git.missingReports); sb.AppendLine("## Notes"); sb.AppendLine(); sb.AppendLine(string.IsNullOrEmpty(git.notes) ? "Best-effort import only. Missing Git reports are allowed." : git.notes); WriteReport("RL_Unity_Git_Report_Summary.md", sb.ToString()); } private static void WriteProjectSummary(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Project Summary"); sb.AppendLine(); sb.AppendLine("- Project name: " + data.projectName); sb.AppendLine("- Unity version: " + data.unityVersion); sb.AppendLine("- Generated at: " + data.generatedAt); sb.AppendLine("- Scenes analyzed: " + data.scenes.Count); sb.AppendLine("- Scripts analyzed: " + data.scripts.Count); sb.AppendLine("- Prefabs analyzed: " + data.prefabs.Count); sb.AppendLine("- Missing scripts detected: " + CountMissingScripts(data)); sb.AppendLine("- Missing references detected: " + CountMissingReferences(data)); sb.AppendLine(); sb.AppendLine("This report is best-effort and needs manual confirmation before project changes are made."); WriteReport("RL_Unity_Project_Summary.md", sb.ToString()); } private static void WriteSceneMap(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Scene Map"); sb.AppendLine(); foreach (RLSceneInfo scene in data.scenes) { sb.AppendLine("## " + scene.sceneName); sb.AppendLine(); sb.AppendLine("- Scene path: `" + scene.scenePath + "`"); sb.AppendLine("- Build index: " + scene.buildIndex); sb.AppendLine("- Root GameObjects: " + scene.rootGameObjectCount); sb.AppendLine("- Total GameObjects: " + scene.totalGameObjects); sb.AppendLine("- Active/Inactive: " + scene.activeGameObjects + "/" + scene.inactiveGameObjects); sb.AppendLine("- Missing scripts: " + scene.missingScriptCount); sb.AppendLine("- Missing references: " + scene.missingReferenceCount); AppendList(sb, "Root GameObjects", scene.rootGameObjects); if (!string.IsNullOrEmpty(scene.notes)) { sb.AppendLine("Notes: " + scene.notes); sb.AppendLine(); } } WriteReport("RL_Unity_Scene_Map.md", sb.ToString()); } private static void WriteGameObjectMap(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity GameObject Map"); sb.AppendLine(); foreach (RLSceneInfo scene in data.scenes) { sb.AppendLine("## Scene: " + scene.sceneName); sb.AppendLine(); foreach (RLGameObjectInfo go in scene.gameObjects) { sb.AppendLine("- `" + go.hierarchyPath + "` | active=" + go.activeSelf + " | tag=" + go.tag + " | layer=" + go.layer + " | components=" + string.Join(", ", go.components.ToArray())); } sb.AppendLine(); } WriteReport("RL_Unity_GameObject_Map.md", sb.ToString()); } private static void WriteScriptMap(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Script Map"); sb.AppendLine(); foreach (RLScriptInfo script in data.scripts) { sb.AppendLine("## `" + script.scriptPath + "`"); sb.AppendLine(); sb.AppendLine("- Class: " + script.className); sb.AppendLine("- Base class: " + script.baseClass); sb.AppendLine("- MonoBehaviour: " + BoolText(script.isMonoBehaviour)); sb.AppendLine("- ScriptableObject: " + BoolText(script.isScriptableObject)); sb.AppendLine("- Possible role: " + script.possibleRole); AppendList(sb, "Lifecycle Methods", script.lifecycleMethods); AppendList(sb, "Public Methods", script.publicMethods); AppendList(sb, "Serialized Fields", script.serializedFields); AppendList(sb, "Public Fields", script.publicFields); AppendList(sb, "Unity API Usage", script.unityApiUsage); } WriteReport("RL_Unity_Script_Map.md", sb.ToString()); } private static void WriteEventMap(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Event Map"); sb.AppendLine(); bool any = false; foreach (RLSceneInfo scene in data.scenes) { foreach (RLUnityEventInfo evt in scene.unityEvents) { any = true; sb.AppendLine("- Scene `" + scene.scenePath + "` GameObject `" + evt.gameObjectPath + "` component `" + evt.componentName + "` event `" + evt.eventName + "` targets `" + evt.targetObject + "." + evt.targetMethod + "`"); } } if (!any) { sb.AppendLine("No UnityEvent or Button callback bindings detected in analyzed scenes."); } WriteReport("RL_Unity_Event_Map.md", sb.ToString()); } private static void WritePrefabMap(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Prefab Map"); sb.AppendLine(); foreach (RLPrefabInfo prefab in data.prefabs) { sb.AppendLine("## `" + prefab.prefabPath + "`"); sb.AppendLine(); AppendList(sb, "Components", prefab.components); AppendList(sb, "Attached Scripts", prefab.attachedScripts); AppendList(sb, "Nested Prefabs", prefab.nestedPrefabs); AppendList(sb, "Important References", prefab.importantReferences); sb.AppendLine("- Missing scripts: " + prefab.missingScriptCount); sb.AppendLine("- Missing references: " + prefab.missingReferenceCount); sb.AppendLine(); } WriteReport("RL_Unity_Prefab_Map.md", sb.ToString()); } private static void WriteExecutionOrder(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Execution Order"); sb.AppendLine(); sb.AppendLine("## Script Execution Order Settings"); sb.AppendLine(); bool any = false; string[] scriptGuids = AssetDatabase.FindAssets("t:MonoScript"); foreach (string guid in scriptGuids) { string path = AssetDatabase.GUIDToAssetPath(guid); MonoScript script = AssetDatabase.LoadAssetAtPath(path); if (script == null) { continue; } int order = MonoImporter.GetExecutionOrder(script); if (order != 0) { any = true; sb.AppendLine("- " + script.name + ": " + order); } } if (!any) { sb.AppendLine("- No custom script execution order detected."); } sb.AppendLine(); sb.AppendLine("## Probable Runtime Order"); sb.AppendLine(); sb.AppendLine("- Scene load"); sb.AppendLine("- Awake"); sb.AppendLine("- OnEnable"); sb.AppendLine("- Start"); sb.AppendLine("- Update / FixedUpdate / LateUpdate"); sb.AppendLine("- UnityEvents / UI callbacks"); sb.AppendLine("- Scene transitions"); AppendList(sb, "Lifecycle Methods By Script", BuildLifecycleLines(data.scripts)); WriteReport("RL_Unity_Execution_Order.md", sb.ToString()); } private static void WriteMissingReferences(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Missing References"); sb.AppendLine(); foreach (RLSceneInfo scene in data.scenes) { foreach (RLGameObjectInfo go in scene.gameObjects) { if (go.missingScriptCount > 0 || go.missingReferenceCount > 0) { sb.AppendLine("- Scene `" + scene.scenePath + "` GameObject `" + go.hierarchyPath + "` missing scripts=" + go.missingScriptCount + " missing references=" + go.missingReferenceCount); } } } foreach (RLPrefabInfo prefab in data.prefabs) { if (prefab.missingScriptCount > 0 || prefab.missingReferenceCount > 0) { sb.AppendLine("- Prefab `" + prefab.prefabPath + "` missing scripts=" + prefab.missingScriptCount + " missing references=" + prefab.missingReferenceCount); } } if (sb.ToString().TrimEnd().EndsWith("References", StringComparison.Ordinal)) { sb.AppendLine("No missing scripts or missing serialized object references detected in analyzed assets."); } WriteReport("RL_Unity_Missing_References.md", sb.ToString()); } private static void WriteProjectDataJson(RLProjectAnalysisData data) { WriteReport("RL_Unity_Project_Data.json", JsonUtility.ToJson(data, true)); } private static void WriteAIContext(RLProjectAnalysisData data) { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel AI Project Context"); sb.AppendLine(); sb.AppendLine("This report is intended to be fed into AI/Codex before deeper Unity project work."); sb.AppendLine(); sb.AppendLine("- Project name: " + data.projectName); sb.AppendLine("- Unity version: " + data.unityVersion); sb.AppendLine("- Main likely entry scene: " + GetLikelyEntryScene(data)); sb.AppendLine("- Git validation status: " + (data.gitReports.validationFoundErrors ? "Detected customer code errors" : "No customer code errors detected or report missing")); AppendList(sb, "Scenes", SceneLines(data.scenes)); AppendList(sb, "Important Managers/Controllers", ManagerLines(data.scripts)); AppendList(sb, "MonoBehaviour Scripts", ScriptLines(data.scripts, true)); AppendList(sb, "ScriptableObjects", ScriptLines(data.scripts, false)); AppendList(sb, "Prefabs", PrefabLines(data.prefabs)); AppendList(sb, "Scene Transitions", UsageLines(data.scripts, "SceneManager")); AppendList(sb, "Suggested First Files For AI To Inspect", data.suggestedFirstFiles); AppendList(sb, "Suggested Risks Or Unknowns", data.risksOrUnknowns); sb.AppendLine("## Limitations"); sb.AppendLine(); sb.AppendLine("- Best-effort static and editor inspection only."); sb.AppendLine("- Runtime-created objects, reflection, dependency injection, addressable content, and generated code may be incomplete."); sb.AppendLine("- Findings need manual confirmation before changes are made."); WriteReport("RL_AI_Project_Context.md", sb.ToString()); WriteReport("RL_AI_Project_Context.json", JsonUtility.ToJson(data, true)); } private static void WriteReportIndex() { StringBuilder sb = new StringBuilder(); sb.AppendLine("# Runlevel Unity Report Index"); sb.AppendLine(); sb.AppendLine("- `RL_Unity_Project_Summary.md`: high-level project counts and status."); sb.AppendLine("- `RL_Unity_Scene_Map.md`: scene paths, roots, counts, and missing reference totals."); sb.AppendLine("- `RL_Unity_GameObject_Map.md`: GameObject hierarchy and component overview."); sb.AppendLine("- `RL_Unity_Script_Map.md`: C# script lifecycle, fields, methods, and Unity API usage."); sb.AppendLine("- `RL_Unity_Event_Map.md`: detected UnityEvent and Button callback bindings."); sb.AppendLine("- `RL_Unity_Prefab_Map.md`: prefab components, scripts, nested prefabs, and missing references."); sb.AppendLine("- `RL_Unity_Execution_Order.md`: script execution order settings and probable lifecycle order."); sb.AppendLine("- `RL_Unity_Missing_References.md`: missing scripts and serialized reference issues."); sb.AppendLine("- `RL_Unity_Git_Report_Summary.md`: imported root `reports/` summary if available."); sb.AppendLine("- `RL_Unity_Project_Data.json`: AI-readable Unity analysis data."); sb.AppendLine("- `RL_AI_Project_Context.md`: primary AI/Codex context report."); sb.AppendLine("- `RL_AI_Project_Context.json`: machine-readable AI context data."); WriteReport("RL_Unity_Report_Index.md", sb.ToString()); } public static void WriteReport(string fileName, string contents) { EnsureFolders(); File.WriteAllText(Path.Combine(AbsoluteReportFolder, fileName), contents, Encoding.UTF8); } public static void RevealReportFolder() { EnsureFolders(); EditorUtility.RevealInFinder(AbsoluteReportFolder); } private static void EnsureAssetFolder(string parent, string child) { string full = parent + "/" + child; if (!AssetDatabase.IsValidFolder(full)) { AssetDatabase.CreateFolder(parent, child); } } private static string BoolText(bool value) { return value ? "yes" : "no"; } private static string ValueOrUnknown(int value) { return value >= 0 ? value.ToString() : "unknown"; } private static void AppendList(StringBuilder sb, string title, List values) { sb.AppendLine("## " + title); sb.AppendLine(); if (values == null || values.Count == 0) { sb.AppendLine("- None detected."); } else { foreach (string value in values) { sb.AppendLine("- " + value); } } sb.AppendLine(); } private static int CountMissingScripts(RLProjectAnalysisData data) { int count = 0; foreach (RLSceneInfo scene in data.scenes) count += scene.missingScriptCount; foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingScriptCount; return count; } private static int CountMissingReferences(RLProjectAnalysisData data) { int count = 0; foreach (RLSceneInfo scene in data.scenes) count += scene.missingReferenceCount; foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingReferenceCount; return count; } private static List BuildLifecycleLines(List scripts) { List lines = new List(); foreach (RLScriptInfo script in scripts) { if (script.lifecycleMethods.Count > 0) { lines.Add(script.className + ": " + string.Join(", ", script.lifecycleMethods.ToArray())); } } return lines; } private static string GetLikelyEntryScene(RLProjectAnalysisData data) { foreach (RLSceneInfo scene in data.scenes) { if (scene.buildIndex == 0) return scene.scenePath; } return data.scenes.Count > 0 ? data.scenes[0].scenePath : "No scene analyzed"; } private static List SceneLines(List scenes) { List lines = new List(); foreach (RLSceneInfo scene in scenes) lines.Add(scene.scenePath + " (" + scene.totalGameObjects + " GameObjects)"); return lines; } private static List ManagerLines(List scripts) { List lines = new List(); foreach (RLScriptInfo script in scripts) { string lower = (script.className + " " + script.possibleRole).ToLowerInvariant(); if (lower.Contains("manager") || lower.Contains("controller")) { lines.Add(script.scriptPath + " - " + script.possibleRole); } } return lines; } private static List ScriptLines(List scripts, bool monoBehaviour) { List lines = new List(); foreach (RLScriptInfo script in scripts) { if ((monoBehaviour && script.isMonoBehaviour) || (!monoBehaviour && script.isScriptableObject)) { lines.Add(script.scriptPath + " - " + script.className); } } return lines; } private static List PrefabLines(List prefabs) { List lines = new List(); foreach (RLPrefabInfo prefab in prefabs) lines.Add(prefab.prefabPath); return lines; } private static List UsageLines(List scripts, string usage) { List lines = new List(); foreach (RLScriptInfo script in scripts) { if (script.unityApiUsage.Contains(usage)) { lines.Add(script.scriptPath); } } return lines; } } }