added unity files
All checks were successful
Code Documentation / code-docs (push) Successful in 6s
Project Report / project-report (push) Successful in 4s
Validate Code / validate (push) Successful in 3s

This commit is contained in:
Frank Harris 2026-06-15 12:03:33 -05:00
parent 9042333f51
commit 28c15035a4
62 changed files with 3873 additions and 270 deletions

View file

@ -0,0 +1,47 @@
name: Code Documentation
on:
push:
pull_request:
jobs:
code-docs:
runs-on: linux-dev
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Generate code documentation
shell: bash
run: |
chmod +x tools/generate-code-docs.sh
./tools/generate-code-docs.sh
- name: Show code documentation summary
shell: bash
run: |
test -f reports/api-reference.md
test -f reports/file-purpose.md
test -f reports/unity-scripts.md
test -f reports/call-map.md
test -f reports/code-docs-summary.md
echo "===== CODE DOCUMENTATION COMPLETE ====="
echo "Files created or updated:"
echo "- reports/api-reference.md"
echo "- reports/file-purpose.md"
echo "- reports/unity-scripts.md"
echo "- reports/call-map.md"
echo "- reports/code-docs-summary.md"
echo "Detected code types:"
awk '
/^Detected code types:/ {show=1; next}
show && /^-/ {print}
show && NF == 0 {exit}
' reports/code-docs-summary.md
echo
echo "===== CODE DOCS SUMMARY ====="
cat reports/code-docs-summary.md

View file

@ -12,7 +12,53 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run validation report pass
shell: bash
run: |
chmod +x tools/validate-code.sh
./tools/validate-code.sh
- name: Generate project report
shell: bash
run: |
chmod +x tools/project-report.sh
./tools/project-report.sh
- name: Show project report summary
shell: bash
run: |
test -f reports/project-flow.md
test -f reports/file-summary.md
test -f reports/languages.md
echo "===== PROJECT REPORT COMPLETE ====="
echo "Report generation completed"
echo "Files created or updated:"
if test -f reports/generated-files.md; then
sed -n '3,$p' reports/generated-files.md
else
echo "- reports/project-flow.md"
echo "- reports/file-summary.md"
echo "- reports/languages.md"
fi
echo "Customer code status:"
if test -f reports/customer-code-status.txt; then
cat reports/customer-code-status.txt
elif test -f reports/errors.md && grep -q "CUSTOMER CODE ERRORS FOUND\|NO CUSTOMER CODE ERRORS FOUND" reports/errors.md; then
grep -m 1 "CUSTOMER CODE ERRORS FOUND\|NO CUSTOMER CODE ERRORS FOUND" reports/errors.md
else
echo "CUSTOMER CODE STATUS UNKNOWN"
fi
if test -f reports/errors.md; then
echo
echo "===== VALIDATION SUMMARY EXCERPT ====="
sed -n '1,12p' reports/errors.md
fi
if test -f reports/summary.md; then
echo
echo "===== AUTOMATION SUMMARY ====="
cat reports/summary.md
fi

View file

@ -13,6 +13,40 @@ jobs:
uses: actions/checkout@v4
- name: Run validation
shell: bash
run: |
chmod +x tools/validate-code.sh
./tools/validate-code.sh
- name: Show validation reports
shell: bash
run: |
test -f reports/errors.md
echo "===== VALIDATION SUMMARY ====="
if test -f reports/customer-code-status.txt; then
cat reports/customer-code-status.txt
elif grep -q "CUSTOMER CODE ERRORS FOUND\|NO CUSTOMER CODE ERRORS FOUND" reports/errors.md; then
grep -m 1 "CUSTOMER CODE ERRORS FOUND\|NO CUSTOMER CODE ERRORS FOUND" reports/errors.md
else
echo "CUSTOMER CODE STATUS UNKNOWN"
fi
echo "Reports written:"
if test -f reports/generated-files.md; then
sed -n '3,$p' reports/generated-files.md
else
echo "- reports/errors.md"
fi
echo
echo "===== VALIDATION REPORT FILES ====="
if test -f reports/generated-files.md; then
cat reports/generated-files.md
else
echo "- reports/errors.md"
fi
echo
echo "===== reports/errors.md ====="
cat reports/errors.md

View file

@ -0,0 +1,34 @@
# AI Context
The primary AI-ready file is:
`Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md`
This file is intended to be pasted into or read by AI/Codex before deeper work on a customer Unity project.
It summarizes:
- project name
- Unity version
- scenes
- likely entry scene
- GameObjects
- managers/controllers
- MonoBehaviour scripts
- ScriptableObjects
- lifecycle methods
- public methods
- serialized fields
- UnityEvents
- scene transitions
- prefabs
- missing scripts and references
- imported Git report status
- suggested first files to inspect
- risks and unknowns
The JSON companion file is:
`Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.json`
Use cautious interpretation. The report is designed to accelerate understanding, not replace manual review.

View file

@ -0,0 +1,30 @@
# Codex Instructions For Unity Analyzer
## Purpose
This Unity package gives Runlevel Systems DevPlus1 a safe Editor-only analyzer for customer Unity projects.
## Rules
- Read generated reports before changing customer Unity code.
- Treat findings as best-effort and confirm manually.
- Do not modify customer scenes, prefabs, scripts, assets, or project settings unless explicitly asked.
- Do not generate C# files into `Assets/RL_DevPlus1/Reports/`.
- Keep analyzer Editor scripts under `Assets/RL_DevPlus1/Editor/`.
- Keep docs under `Assets/RL_DevPlus1/Docs/`.
- Keep report outputs under `Assets/RL_DevPlus1/Reports/`.
## Recommended AI Reading Order
1. `Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md`
2. `Assets/RL_DevPlus1/Reports/RL_Unity_Report_Index.md`
3. `Assets/RL_DevPlus1/Reports/RL_Unity_Project_Summary.md`
4. `reports/api-reference.md`
5. `reports/call-map.md`
6. `Assets/RL_DevPlus1/Reports/RL_Unity_Scene_Map.md`
7. `Assets/RL_DevPlus1/Reports/RL_Unity_Script_Map.md`
8. `Assets/RL_DevPlus1/Reports/RL_Unity_Event_Map.md`
## Maintenance
Update this file when analyzer behavior, report locations, or recommended reading order changes.

View file

@ -0,0 +1,26 @@
# Runlevel DevPlus1 Unity Analyzer
This folder contains the Unity-side Runlevel Systems DevPlus1 project analyzer.
The analyzer is an Editor-only tool. It reads Unity project structure, scenes, prefabs, scripts, UnityEvents, and existing Git-generated reports, then writes safe markdown and JSON reports under:
`Assets/RL_DevPlus1/Reports/`
It does not modify customer scenes, prefabs, scripts, project settings, or assets.
## Menu Location
Unity menu:
`Runlevel Systems/DevPlus1/Analyze Unity Project`
## Main Reports
- `RL_AI_Project_Context.md`
- `RL_Unity_Report_Index.md`
- `RL_Unity_Project_Summary.md`
- `RL_Unity_Scene_Map.md`
- `RL_Unity_Script_Map.md`
- `RL_Unity_Event_Map.md`
Future Codex sessions should read `RL_AI_Project_Context.md` first after reports are generated.

View file

@ -0,0 +1,28 @@
# Unity Analyzer Reports
Reports are written under:
`Assets/RL_DevPlus1/Reports/`
## Human-Readable Reports
- `RL_Unity_Project_Summary.md`: high-level counts and project status.
- `RL_Unity_Scene_Map.md`: scene paths, root objects, counts, and missing reference totals.
- `RL_Unity_GameObject_Map.md`: GameObject hierarchy, tags, layers, and components.
- `RL_Unity_Script_Map.md`: script classes, lifecycle methods, fields, methods, and Unity API usage.
- `RL_Unity_Event_Map.md`: UnityEvent and Button callback bindings where detectable.
- `RL_Unity_Prefab_Map.md`: prefab components, scripts, nested prefabs, and references.
- `RL_Unity_Execution_Order.md`: custom script execution order and probable lifecycle order.
- `RL_Unity_Missing_References.md`: missing scripts and missing serialized references.
- `RL_Unity_Git_Report_Summary.md`: summary of repository-root Git reports if present.
- `RL_Unity_Report_Index.md`: index of generated reports.
## AI-Ready Reports
- `RL_AI_Project_Context.md`: primary Codex/AI context report.
- `RL_AI_Project_Context.json`: machine-readable AI context.
- `RL_Unity_Project_Data.json`: full machine-readable project analysis data.
## Limitations
Reports are best-effort. Runtime-created objects, reflection, dependency injection, generated content, addressable content, and custom serialization may require manual confirmation.

View file

@ -0,0 +1,47 @@
# Unity Analyzer
## What It Does
The Runlevel DevPlus1 Unity Analyzer creates project-understanding reports for customer Unity projects.
It can inspect:
- open scenes
- build settings scenes
- all asset scenes
- prefabs
- C# scripts
- Unity lifecycle methods
- UnityEvents and Button callbacks where serialized data is visible
- missing scripts
- missing serialized object references where detectable
- root Git-generated reports under `reports/`
## Menu Items
- `Runlevel Systems/DevPlus1/Analyze Unity Project`
- `Runlevel Systems/DevPlus1/Open Report Folder`
- `Runlevel Systems/DevPlus1/Refresh Git Reports`
## Safety
The analyzer is read-only against customer source files and Unity assets.
It does not:
- save scenes
- modify prefabs
- edit scripts
- write generated C# files
- modify project settings
- create asmdef files
If open scenes have unsaved changes, broad scene-scan operations skip scene switching rather than saving or discarding changes.
## How To Run
1. Open the Unity project.
2. Wait for Unity to compile Editor scripts.
3. Open `Runlevel Systems/DevPlus1/Analyze Unity Project`.
4. Click `Generate All Reports`.
5. Open `Assets/RL_DevPlus1/Reports/`.

View file

@ -0,0 +1,69 @@
using System.Collections.Generic;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLAIContextExporter
{
public static void EnrichProjectContext(RLProjectAnalysisData data)
{
data.suggestedFirstFiles.Clear();
data.risksOrUnknowns.Clear();
foreach (RLScriptInfo script in data.scripts)
{
string lower = (script.className + " " + script.possibleRole).ToLowerInvariant();
if (lower.Contains("manager") || lower.Contains("controller") || script.lifecycleMethods.Count > 0)
{
AddUnique(data.suggestedFirstFiles, script.scriptPath);
}
}
foreach (string file in data.gitReports.importantFiles)
{
AddUnique(data.suggestedFirstFiles, file);
}
if (data.scenes.Count == 0)
{
data.risksOrUnknowns.Add("No scenes were analyzed. Scene flow needs manual confirmation.");
}
if (data.gitReports.validationFoundErrors)
{
data.risksOrUnknowns.Add("Git validation reports customer code errors. Review reports/errors.md before feature work.");
}
int missingScripts = 0;
int missingReferences = 0;
foreach (RLSceneInfo scene in data.scenes)
{
missingScripts += scene.missingScriptCount;
missingReferences += scene.missingReferenceCount;
}
foreach (RLPrefabInfo prefab in data.prefabs)
{
missingScripts += prefab.missingScriptCount;
missingReferences += prefab.missingReferenceCount;
}
if (missingScripts > 0)
{
data.risksOrUnknowns.Add("Missing scripts were detected. Inspect RL_Unity_Missing_References.md.");
}
if (missingReferences > 0)
{
data.risksOrUnknowns.Add("Missing serialized object references were detected. Inspect RL_Unity_Missing_References.md.");
}
data.risksOrUnknowns.Add("Analysis is best-effort and may miss runtime-created objects, reflection, addressables, or dependency injection.");
}
private static void AddUnique(List<string> values, string value)
{
if (!string.IsNullOrEmpty(value) && !values.Contains(value))
{
values.Add(value);
}
}
}
}

View file

@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLGitReportImporter
{
private static readonly string[] ExpectedReports =
{
"errors.md",
"api-reference.md",
"file-purpose.md",
"call-map.md",
"unity-scripts.md",
"code-docs-summary.md",
"project-flow.md",
"summary.md",
"generated-files.md"
};
public static RLGitReportSummary ImportGitReports()
{
RLGitReportSummary summary = new RLGitReportSummary();
string repoRoot = Directory.GetParent(Application.dataPath).FullName;
string reportsFolder = Path.Combine(repoRoot, "reports");
summary.reportsFolderFound = Directory.Exists(reportsFolder);
if (!summary.reportsFolderFound)
{
summary.notes = "Repository-root reports folder was not found. Run Forgejo reports or local scripts first if Git analysis is needed.";
foreach (string report in ExpectedReports)
{
summary.missingReports.Add("reports/" + report);
}
return summary;
}
foreach (string report in ExpectedReports)
{
string path = Path.Combine(reportsFolder, report);
if (File.Exists(path))
{
summary.foundReports.Add("reports/" + report);
}
else
{
summary.missingReports.Add("reports/" + report);
}
}
string errorsPath = Path.Combine(reportsFolder, "errors.md");
summary.validationReportFound = File.Exists(errorsPath);
if (summary.validationReportFound)
{
string text = File.ReadAllText(errorsPath);
summary.validationFoundErrors = text.Contains("CUSTOMER CODE ERRORS FOUND");
summary.validationWarnings = ExtractCount(text, @"Warnings:\s*(\d+)");
summary.validationErrors = ExtractCount(text, @"Errors:\s*(\d+)");
}
summary.apiReferenceFound = File.Exists(Path.Combine(reportsFolder, "api-reference.md"));
summary.filePurposeFound = File.Exists(Path.Combine(reportsFolder, "file-purpose.md"));
summary.staticCallMapFound = File.Exists(Path.Combine(reportsFolder, "call-map.md"));
summary.unityScriptReportFound = File.Exists(Path.Combine(reportsFolder, "unity-scripts.md"));
ReadProjectFlow(Path.Combine(reportsFolder, "project-flow.md"), summary);
summary.notes = "Imported repository-root Git reports with best-effort parsing.";
return summary;
}
private static int ExtractCount(string text, string pattern)
{
Match match = Regex.Match(text, pattern);
if (match.Success && int.TryParse(match.Groups[1].Value, out int value))
{
return value;
}
return -1;
}
private static void ReadProjectFlow(string path, RLGitReportSummary summary)
{
if (!File.Exists(path))
{
return;
}
string[] lines = File.ReadAllLines(path);
bool inEntry = false;
bool inImportant = false;
foreach (string line in lines)
{
if (line.StartsWith("## "))
{
inEntry = line.Contains("Likely Entry Point") || line.Contains("Possible Startup");
inImportant = line.Contains("Important Files");
continue;
}
if (line.StartsWith("- ") && inEntry)
{
summary.likelyEntryPoints.Add(line.Substring(2));
}
else if (line.StartsWith("- ") && inImportant)
{
summary.importantFiles.Add(line.Substring(2));
}
}
}
}
}

View file

@ -0,0 +1,98 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLPrefabAnalyzer
{
public static List<RLPrefabInfo> AnalyzePrefabs()
{
List<RLPrefabInfo> results = new List<RLPrefabInfo>();
string[] guids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null)
{
continue;
}
RLPrefabInfo info = new RLPrefabInfo();
info.prefabPath = path;
TraversePrefab(prefab, info);
results.Add(info);
}
return results;
}
private static void TraversePrefab(GameObject gameObject, RLPrefabInfo info)
{
info.missingScriptCount += GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
AddUnique(info.components, "Missing Script");
continue;
}
AddUnique(info.components, component.GetType().Name);
if (component is MonoBehaviour)
{
AddUnique(info.attachedScripts, component.GetType().Name);
}
CollectReferences(component, info);
}
string prefabSource = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
if (!string.IsNullOrEmpty(prefabSource) && prefabSource != info.prefabPath)
{
AddUnique(info.nestedPrefabs, prefabSource);
}
foreach (Transform child in gameObject.transform)
{
TraversePrefab(child.gameObject, info);
}
}
private static void CollectReferences(Component component, RLPrefabInfo info)
{
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty property = serializedObject.GetIterator();
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
enterChildren = false;
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
continue;
}
if (property.objectReferenceValue != null)
{
AddUnique(info.importantReferences, component.GetType().Name + "." + property.displayName + " -> " + property.objectReferenceValue.name);
}
else if (property.objectReferenceInstanceIDValue != 0)
{
info.missingReferenceCount++;
}
}
}
private static void AddUnique(List<string> values, string value)
{
if (!values.Contains(value))
{
values.Add(value);
}
}
}
}

View file

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public class RLProjectAnalyzerWindow : EditorWindow
{
private RLProjectAnalysisData data = new RLProjectAnalysisData();
private string lastRunTime = "Not run yet";
private Vector2 scroll;
[MenuItem("Runlevel Systems/DevPlus1/Analyze Unity Project")]
public static void ShowWindow()
{
RLProjectAnalyzerWindow window = GetWindow<RLProjectAnalyzerWindow>("Runlevel DevPlus1 Project Analyzer");
window.minSize = new Vector2(520f, 520f);
}
[MenuItem("Runlevel Systems/DevPlus1/Open Report Folder")]
public static void OpenReportFolder()
{
RLReportExporter.RevealReportFolder();
}
[MenuItem("Runlevel Systems/DevPlus1/Refresh Git Reports")]
public static void RefreshGitReportsMenu()
{
RLGitReportSummary git = RLGitReportImporter.ImportGitReports();
RLReportExporter.WriteGitSummary(git);
AssetDatabase.Refresh();
Debug.Log("Runlevel DevPlus1 Git report summary refreshed.");
}
private void OnEnable()
{
RLReportExporter.EnsureFolders();
data.projectName = Application.productName;
data.unityVersion = Application.unityVersion;
}
private void OnGUI()
{
scroll = EditorGUILayout.BeginScrollView(scroll);
EditorGUILayout.LabelField("Runlevel DevPlus1 Project Analyzer", EditorStyles.boldLabel);
EditorGUILayout.Space();
if (GUILayout.Button("Import Git Reports")) ImportGitReports();
if (GUILayout.Button("Analyze Open Scene")) AnalyzeOpenScenes();
if (GUILayout.Button("Analyze Build Settings Scenes")) AnalyzeBuildSettingsScenes();
if (GUILayout.Button("Analyze All Asset Scenes")) AnalyzeAllAssetScenes();
if (GUILayout.Button("Analyze Prefabs")) AnalyzePrefabs();
if (GUILayout.Button("Analyze Scripts")) AnalyzeScripts();
if (GUILayout.Button("Generate All Reports")) GenerateAllReports();
if (GUILayout.Button("Open Reports Folder")) RLReportExporter.RevealReportFolder();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Last run time", lastRunTime);
EditorGUILayout.LabelField("Scenes analyzed", data.scenes.Count.ToString());
EditorGUILayout.LabelField("Scripts analyzed", data.scripts.Count.ToString());
EditorGUILayout.LabelField("Prefabs analyzed", data.prefabs.Count.ToString());
EditorGUILayout.LabelField("Missing script count", CountMissingScripts().ToString());
EditorGUILayout.LabelField("Missing reference count", CountMissingReferences().ToString());
EditorGUILayout.LabelField("Report folder path", RLReportExporter.AbsoluteReportFolder);
EditorGUILayout.EndScrollView();
}
private void ImportGitReports()
{
data.gitReports = RLGitReportImporter.ImportGitReports();
RLReportExporter.WriteGitSummary(data.gitReports);
MarkRun();
}
private void AnalyzeOpenScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeOpenScenes();
MarkRun();
}
private void AnalyzeBuildSettingsScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeBuildSettingsScenes();
MarkRun();
}
private void AnalyzeAllAssetScenes()
{
data.scenes = RLSceneAnalyzer.AnalyzeAllAssetScenes();
MarkRun();
}
private void AnalyzePrefabs()
{
data.prefabs = RLPrefabAnalyzer.AnalyzePrefabs();
MarkRun();
}
private void AnalyzeScripts()
{
data.scripts = RLScriptAnalyzer.AnalyzeScripts();
MarkRun();
}
private void GenerateAllReports()
{
data.projectName = Application.productName;
data.unityVersion = Application.unityVersion;
data.generatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
data.gitReports = RLGitReportImporter.ImportGitReports();
data.scenes = RLSceneAnalyzer.AnalyzeOpenScenes();
data.prefabs = RLPrefabAnalyzer.AnalyzePrefabs();
data.scripts = RLScriptAnalyzer.AnalyzeScripts();
RLAIContextExporter.EnrichProjectContext(data);
RLReportExporter.ExportAll(data);
MarkRun();
Debug.Log("Runlevel DevPlus1 Unity reports generated.");
}
private void MarkRun()
{
lastRunTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Repaint();
}
private int CountMissingScripts()
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingScriptCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingScriptCount;
return count;
}
private int CountMissingReferences()
{
int count = 0;
foreach (RLSceneInfo scene in data.scenes) count += scene.missingReferenceCount;
foreach (RLPrefabInfo prefab in data.prefabs) count += prefab.missingReferenceCount;
return count;
}
}
}

View file

@ -0,0 +1,567 @@
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<RLSceneInfo> scenes = new List<RLSceneInfo>();
public List<RLScriptInfo> scripts = new List<RLScriptInfo>();
public List<RLPrefabInfo> prefabs = new List<RLPrefabInfo>();
public RLGitReportSummary gitReports = new RLGitReportSummary();
public List<string> suggestedFirstFiles = new List<string>();
public List<string> risksOrUnknowns = new List<string>();
}
[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<string> rootGameObjects = new List<string>();
public List<RLGameObjectInfo> gameObjects = new List<RLGameObjectInfo>();
public List<RLUnityEventInfo> unityEvents = new List<RLUnityEventInfo>();
public string notes;
}
[Serializable]
public class RLGameObjectInfo
{
public string hierarchyPath;
public bool activeSelf;
public string tag;
public int layer;
public List<string> components = new List<string>();
public List<string> attachedScripts = new List<string>();
public List<string> referencedObjects = new List<string>();
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<string> lifecycleMethods = new List<string>();
public List<string> publicMethods = new List<string>();
public List<string> serializedFields = new List<string>();
public List<string> publicFields = new List<string>();
public List<string> unityApiUsage = new List<string>();
public string possibleRole;
}
[Serializable]
public class RLPrefabInfo
{
public string prefabPath;
public List<string> components = new List<string>();
public List<string> attachedScripts = new List<string>();
public List<string> nestedPrefabs = new List<string>();
public List<string> importantReferences = new List<string>();
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<string> likelyEntryPoints = new List<string>();
public List<string> importantFiles = new List<string>();
public List<string> foundReports = new List<string>();
public List<string> missingReports = new List<string>();
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<MonoScript>(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<string> 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<string> BuildLifecycleLines(List<RLScriptInfo> scripts)
{
List<string> lines = new List<string>();
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<string> SceneLines(List<RLSceneInfo> scenes)
{
List<string> lines = new List<string>();
foreach (RLSceneInfo scene in scenes) lines.Add(scene.scenePath + " (" + scene.totalGameObjects + " GameObjects)");
return lines;
}
private static List<string> ManagerLines(List<RLScriptInfo> scripts)
{
List<string> lines = new List<string>();
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<string> ScriptLines(List<RLScriptInfo> scripts, bool monoBehaviour)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
if ((monoBehaviour && script.isMonoBehaviour) || (!monoBehaviour && script.isScriptableObject))
{
lines.Add(script.scriptPath + " - " + script.className);
}
}
return lines;
}
private static List<string> PrefabLines(List<RLPrefabInfo> prefabs)
{
List<string> lines = new List<string>();
foreach (RLPrefabInfo prefab in prefabs) lines.Add(prefab.prefabPath);
return lines;
}
private static List<string> UsageLines(List<RLScriptInfo> scripts, string usage)
{
List<string> lines = new List<string>();
foreach (RLScriptInfo script in scripts)
{
if (script.unityApiUsage.Contains(usage))
{
lines.Add(script.scriptPath);
}
}
return lines;
}
}
}

View file

@ -0,0 +1,193 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLSceneAnalyzer
{
public static List<RLSceneInfo> AnalyzeOpenScenes()
{
List<RLSceneInfo> results = new List<RLSceneInfo>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.IsValid() && scene.isLoaded)
{
results.Add(AnalyzeLoadedScene(scene));
}
}
return results;
}
public static List<RLSceneInfo> AnalyzeBuildSettingsScenes()
{
List<string> paths = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (scene.enabled)
{
paths.Add(scene.path);
}
}
return AnalyzeScenePaths(paths);
}
public static List<RLSceneInfo> AnalyzeAllAssetScenes()
{
List<string> paths = new List<string>();
foreach (string guid in AssetDatabase.FindAssets("t:Scene"))
{
paths.Add(AssetDatabase.GUIDToAssetPath(guid));
}
return AnalyzeScenePaths(paths);
}
private static List<RLSceneInfo> AnalyzeScenePaths(List<string> paths)
{
List<RLSceneInfo> results = new List<RLSceneInfo>();
if (HasDirtyOpenScenes())
{
RLSceneInfo skipped = new RLSceneInfo();
skipped.sceneName = "Scene analysis skipped";
skipped.scenePath = "Open scenes contain unsaved changes";
skipped.notes = "The analyzer does not save scenes. Save or discard changes manually, then run scene analysis again.";
results.Add(skipped);
return results;
}
SceneSetup[] setup = EditorSceneManager.GetSceneManagerSetup();
try
{
foreach (string path in paths)
{
if (string.IsNullOrEmpty(path))
{
continue;
}
Scene scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single);
results.Add(AnalyzeLoadedScene(scene));
}
}
finally
{
if (setup != null && setup.Length > 0)
{
EditorSceneManager.RestoreSceneManagerSetup(setup);
}
}
return results;
}
private static bool HasDirtyOpenScenes()
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.isDirty)
{
return true;
}
}
return false;
}
private static RLSceneInfo AnalyzeLoadedScene(Scene scene)
{
RLSceneInfo info = new RLSceneInfo();
info.scenePath = scene.path;
info.sceneName = scene.name;
info.buildIndex = scene.buildIndex;
GameObject[] roots = scene.GetRootGameObjects();
info.rootGameObjectCount = roots.Length;
foreach (GameObject root in roots)
{
info.rootGameObjects.Add(root.name);
AnalyzeGameObjectRecursive(root, root.name, scene.path, info);
}
return info;
}
public static void AnalyzeGameObjectRecursive(GameObject gameObject, string hierarchyPath, string sourcePath, RLSceneInfo sceneInfo)
{
RLGameObjectInfo goInfo = AnalyzeGameObject(gameObject, hierarchyPath);
sceneInfo.gameObjects.Add(goInfo);
sceneInfo.totalGameObjects++;
if (gameObject.activeSelf) sceneInfo.activeGameObjects++;
else sceneInfo.inactiveGameObjects++;
sceneInfo.missingScriptCount += goInfo.missingScriptCount;
sceneInfo.missingReferenceCount += goInfo.missingReferenceCount;
sceneInfo.unityEvents.AddRange(RLUnityEventAnalyzer.ExtractUnityEvents(gameObject, sourcePath, hierarchyPath));
foreach (Transform child in gameObject.transform)
{
AnalyzeGameObjectRecursive(child.gameObject, hierarchyPath + "/" + child.name, sourcePath, sceneInfo);
}
}
public static RLGameObjectInfo AnalyzeGameObject(GameObject gameObject, string hierarchyPath)
{
RLGameObjectInfo info = new RLGameObjectInfo();
info.hierarchyPath = hierarchyPath;
info.activeSelf = gameObject.activeSelf;
info.tag = gameObject.tag;
info.layer = gameObject.layer;
info.missingScriptCount = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject);
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
info.components.Add("Missing Script");
continue;
}
info.components.Add(component.GetType().Name);
MonoBehaviour monoBehaviour = component as MonoBehaviour;
if (monoBehaviour != null)
{
info.attachedScripts.Add(component.GetType().Name);
}
CollectSerializedReferences(component, info);
}
return info;
}
private static void CollectSerializedReferences(Component component, RLGameObjectInfo info)
{
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty property = serializedObject.GetIterator();
bool enterChildren = true;
while (property.NextVisible(enterChildren))
{
enterChildren = false;
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
continue;
}
if (property.objectReferenceValue != null)
{
string referenceName = property.displayName + " -> " + property.objectReferenceValue.name;
if (!info.referencedObjects.Contains(referenceName))
{
info.referencedObjects.Add(referenceName);
}
}
else if (property.objectReferenceInstanceIDValue != 0)
{
info.missingReferenceCount++;
}
}
}
}
}

View file

@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLScriptAnalyzer
{
private static readonly string[] LifecycleMethods =
{
"Awake", "OnEnable", "Start", "Update", "FixedUpdate", "LateUpdate",
"OnDisable", "OnDestroy", "OnTriggerEnter", "OnTriggerExit",
"OnCollisionEnter", "OnCollisionExit"
};
private static readonly string[] UnityApiMarkers =
{
"SceneManager", "Instantiate", "Destroy", "GetComponent",
"FindObjectOfType", "FindFirstObjectByType", "Resources.Load",
"Addressables", "Input", "UnityEvent"
};
public static List<RLScriptInfo> AnalyzeScripts()
{
List<RLScriptInfo> results = new List<RLScriptInfo>();
string[] guids = AssetDatabase.FindAssets("t:MonoScript");
foreach (string guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.StartsWith("Assets/RL_DevPlus1/Editor/"))
{
continue;
}
MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (monoScript == null)
{
continue;
}
string text = File.Exists(path) ? File.ReadAllText(path) : string.Empty;
System.Type scriptClass = monoScript.GetClass();
RLScriptInfo info = new RLScriptInfo();
info.scriptPath = path;
info.className = scriptClass != null ? scriptClass.Name : GuessClassName(text);
info.baseClass = scriptClass != null && scriptClass.BaseType != null ? scriptClass.BaseType.Name : GuessBaseClass(text);
info.isMonoBehaviour = scriptClass != null && typeof(UnityEngine.MonoBehaviour).IsAssignableFrom(scriptClass);
info.isScriptableObject = scriptClass != null && typeof(UnityEngine.ScriptableObject).IsAssignableFrom(scriptClass);
info.lifecycleMethods.AddRange(FindLifecycleMethods(text));
info.publicMethods.AddRange(FindPublicMethods(text));
info.serializedFields.AddRange(FindSerializedFields(text));
info.publicFields.AddRange(FindPublicFields(text));
info.unityApiUsage.AddRange(FindUnityApiUsage(text));
info.possibleRole = GuessRole(path, info);
results.Add(info);
}
return results;
}
private static string GuessClassName(string text)
{
Match match = Regex.Match(text, @"class\s+([A-Za-z_][A-Za-z0-9_]*)");
return match.Success ? match.Groups[1].Value : "not detected";
}
private static string GuessBaseClass(string text)
{
Match match = Regex.Match(text, @"class\s+[A-Za-z_][A-Za-z0-9_]*\s*:\s*([A-Za-z_][A-Za-z0-9_]*)");
return match.Success ? match.Groups[1].Value : "not detected";
}
private static List<string> FindLifecycleMethods(string text)
{
List<string> results = new List<string>();
foreach (string method in LifecycleMethods)
{
if (Regex.IsMatch(text, @"\b" + method + @"\s*\("))
{
results.Add(method);
}
}
return results;
}
private static List<string> FindPublicMethods(string text)
{
return UniqueMatches(text, @"public\s+[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(");
}
private static List<string> FindSerializedFields(string text)
{
return UniqueMatches(text, @"\[SerializeField\]\s*(private|protected|public)?\s*[A-Za-z_][A-Za-z0-9_<>,\[\]]*\s+([A-Za-z_][A-Za-z0-9_]*)", 2);
}
private static List<string> FindPublicFields(string text)
{
return UniqueMatches(text, @"public\s+[A-Za-z_][A-Za-z0-9_<>,\[\]]*\s+([A-Za-z_][A-Za-z0-9_]*)\s*(=|;)");
}
private static List<string> FindUnityApiUsage(string text)
{
List<string> results = new List<string>();
foreach (string marker in UnityApiMarkers)
{
if (text.Contains(marker))
{
results.Add(marker);
}
}
return results;
}
private static List<string> UniqueMatches(string text, string pattern, int group = 1)
{
List<string> results = new List<string>();
foreach (Match match in Regex.Matches(text, pattern))
{
string value = match.Groups[group].Value;
if (!string.IsNullOrEmpty(value) && !results.Contains(value))
{
results.Add(value);
}
}
return results;
}
private static string GuessRole(string path, RLScriptInfo info)
{
string value = (path + " " + info.className).ToLowerInvariant();
if (value.Contains("player")) return "Possible player controller";
if (value.Contains("enemy") || value.Contains("ai")) return "Possible enemy AI";
if (value.Contains("ui") || value.Contains("menu") || value.Contains("hud")) return "Possible UI controller";
if (value.Contains("game") && value.Contains("manager")) return "Possible game manager";
if (value.Contains("spawner") || value.Contains("spawn")) return "Possible spawner";
if (value.Contains("inventory")) return "Possible inventory system";
if (value.Contains("health") || value.Contains("damage")) return "Possible health/damage system";
if (value.Contains("camera")) return "Possible camera controller";
if (value.Contains("audio") || value.Contains("music")) return "Possible audio manager";
if (value.Contains("scene")) return "Possible scene loader";
if (info.isScriptableObject) return "Possible data/config asset";
return "Possible utility or gameplay script";
}
}
}

View file

@ -0,0 +1,77 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace RunlevelSystems.DevPlus1.EditorTools
{
public static class RLUnityEventAnalyzer
{
public static List<RLUnityEventInfo> ExtractUnityEvents(GameObject gameObject, string sourcePath, string hierarchyPath)
{
List<RLUnityEventInfo> events = new List<RLUnityEventInfo>();
Component[] components = gameObject.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
continue;
}
SerializedObject serializedObject = new SerializedObject(component);
SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren))
{
enterChildren = false;
if (iterator.propertyType == SerializedPropertyType.Generic &&
(iterator.type.Contains("UnityEvent") || iterator.displayName.Contains("On Click") || iterator.name.Contains("m_OnClick")))
{
ExtractPersistentCalls(events, iterator.Copy(), sourcePath, hierarchyPath, component);
}
}
}
return events;
}
private static void ExtractPersistentCalls(List<RLUnityEventInfo> events, SerializedProperty eventProperty, string sourcePath, string hierarchyPath, Component component)
{
SerializedProperty calls = eventProperty.FindPropertyRelative("m_PersistentCalls.m_Calls");
if (calls == null || !calls.isArray)
{
return;
}
for (int i = 0; i < calls.arraySize; i++)
{
SerializedProperty call = calls.GetArrayElementAtIndex(i);
Object target = ReadObject(call, "m_Target");
string method = ReadString(call, "m_MethodName");
RLUnityEventInfo info = new RLUnityEventInfo();
info.sourcePath = sourcePath;
info.gameObjectPath = hierarchyPath;
info.componentName = component.GetType().Name;
info.eventName = eventProperty.displayName;
info.targetObject = target != null ? target.name : "missing or none";
info.targetComponent = target != null ? target.GetType().Name : "not detected";
info.targetMethod = string.IsNullOrEmpty(method) ? "not detected" : method;
events.Add(info);
}
}
private static Object ReadObject(SerializedProperty parent, string relativePath)
{
SerializedProperty property = parent.FindPropertyRelative(relativePath);
return property != null ? property.objectReferenceValue : null;
}
private static string ReadString(SerializedProperty parent, string relativePath)
{
SerializedProperty property = parent.FindPropertyRelative(relativePath);
return property != null ? property.stringValue : string.Empty;
}
}
}

View file

@ -0,0 +1,5 @@
# Runlevel DevPlus1 Reports
Unity analyzer reports are generated here.
This folder must only contain safe report files such as `.md`, `.txt`, and `.json`.

View file

@ -0,0 +1,3 @@
{
"status": "not generated yet"
}

View file

@ -0,0 +1,3 @@
# Runlevel AI Project Context
Not generated yet. Run `Generate All Reports` from the Unity analyzer. This is the first file future AI/Codex sessions should read after generation.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Event Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Execution Order
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity GameObject Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Git Report Summary
Not generated yet. Use `Runlevel Systems/DevPlus1/Refresh Git Reports` or generate all reports.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Missing References
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Prefab Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
{
"status": "not generated yet"
}

View file

@ -0,0 +1,3 @@
# Runlevel Unity Project Summary
Not generated yet. Run `Runlevel Systems/DevPlus1/Analyze Unity Project` in Unity, then click `Generate All Reports`.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Report Index
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Scene Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,3 @@
# Runlevel Unity Script Map
Not generated yet. Run the Unity analyzer to populate this report.

View file

@ -0,0 +1,4 @@
Runtime scripts are not required for the DevPlus1 analyzer.
The current analyzer is Editor-only and lives under:
Assets/RL_DevPlus1/Editor/

43
docs/ARCHITECTURE.md Normal file
View file

@ -0,0 +1,43 @@
# Architecture
## Overview
This repository uses Forgejo Actions on a `linux-dev` runner to validate customer code and generate project analysis reports.
Green workflow:
Automation completed and reports were generated.
Red workflow:
Automation itself failed, crashed, or required reports were not produced.
## Components
- Forgejo workflows
- `linux-dev` runner
- `tools/validate-code.sh`
- `tools/project-report.sh`
- customer source files
- generated markdown reports
- future deployment automation, kept separate from debugging automation
## Text Diagram
```text
Customer Push / Pull Request
|
v
Forgejo Workflow
|
+--> validate.yml --> tools/validate-code.sh --> reports/errors.md
|
+--> project-report.yml --> tools/validate-code.sh
tools/project-report.sh
--> reports/*.md
|
v
Forgejo Logs print summaries and report excerpts
```
## Future Direction
Later deployment automation may choose to block releases when errors are found. That is intentionally separate from the current DevPlus1 debugging behavior.

View file

@ -1,108 +1,113 @@
# Automation
## Purpose
## Intentional Behavior
This repository includes two Forgejo Actions workflows for reusable customer-project automation:
This repository is for customer debugging projects.
- `validate.yml`: runs repository validation on push and pull request.
- `project-report.yml`: generates best-effort project analysis reports on push and pull request.
Customers are expected to upload broken code.
Because of that:
- Green workflow means automation completed.
- Red workflow means automation failed.
- Customer code errors are listed in `reports/errors.md`.
- Broken customer code does not fail the workflow red.
This behavior is intentional for DevPlus1.
## Workflows
### `validate.yml`
- runs on `push`
- runs on `pull_request`
- uses the `linux-dev` runner label
- checks out the repository
- runs `tools/validate-code.sh`
- fails when validation finds real errors
- Runs on `push`
- Runs on `pull_request`
- Uses runner label `linux-dev`
- Runs `tools/validate-code.sh`
- Prints `reports/errors.md` into Forgejo logs
- Fails only if the validation script crashes or `reports/errors.md` is missing
### `project-report.yml`
- runs on `push`
- runs on `pull_request`
- uses the `linux-dev` runner label
- checks out the repository
- runs `tools/project-report.sh`
- regenerates reports in `reports/`
- does not fail because analysis finds issues or uncertainty
- only fails if the script itself crashes
- Runs on `push`
- Runs on `pull_request`
- Uses runner label `linux-dev`
- Runs `tools/validate-code.sh`
- Runs `tools/project-report.sh`
- Prints report summaries into Forgejo logs
- Fails only if report generation crashes or required report files are missing
### `code-docs.yml`
- Runs on `push`
- Runs on `pull_request`
- Uses runner label `linux-dev`
- Runs `tools/generate-code-docs.sh`
- Prints code documentation summary output into Forgejo logs
- Fails only if the documentation script crashes or required code documentation reports are missing
## Scripts
### `tools/validate-code.sh`
This script recursively scans the repository while excluding common generated or dependency folders such as:
- `.git`
- `.forgejo`
- `node_modules`
- `vendor`
- `Library`
- `Temp`
- `Logs`
- `obj`
- `bin`
When the necessary tools are installed, it validates:
- PHP with `php -l`
- Python with `python3 -m py_compile`
- Ruby with `ruby -c`
- Perl with `perl -c`
- Shell with `bash -n`
- JavaScript with `node --check`
- JSON with `jq`
- YAML with `yamllint`
- HTML with `tidy`
- C# with `dotnet build`
- C/C++ CMake projects with `cmake` configure validation
It writes `reports/errors.md` with:
- validation summary
- files checked
- warnings
- errors
- detailed per-file results
- Recursively scans the repository
- Excludes common dependency and generated directories
- Validates supported files when tools are installed
- Always writes `reports/errors.md`
- Always prints a terminal summary
- Exits `0` even when customer code errors are found
- Exits non-zero only for automation/report generation failure
### `tools/project-report.sh`
This script generates:
- Generates project analysis reports
- Reads customer code error status when available
- Writes summary and generated-file tracking reports
- Exits non-zero only if the script itself cannot generate required outputs
### `tools/generate-code-docs.sh`
- Reads customer source files without modifying them
- Generates best-effort API, file-purpose, Unity lifecycle, and call-map reports
- Uses regex-based extraction and cautious wording
- Exits non-zero only if report generation fails
## Generated Report Files
- `reports/errors.md`
- `reports/project-flow.md`
- `reports/file-summary.md`
- `reports/languages.md`
- `reports/summary.md`
- `reports/generated-files.md`
- `reports/customer-code-status.txt`
- `reports/api-reference.md`
- `reports/file-purpose.md`
- `reports/unity-scripts.md`
- `reports/call-map.md`
- `reports/code-docs-summary.md`
It performs best-effort analysis for:
## Validation Sample Files
- language counts
- likely entry points
- possible startup files
- framework detection
- database detection
- environment files
- config files
- Docker-related files
- probable application flow
The repository includes intentional validator fixtures under `tests/validation-samples/`.
The wording is intentionally cautious. It does not claim certainty.
These files exist to prove:
## Reuse In Future Customer Repositories
- valid samples are accepted
- broken samples are reported
- broken customer code does not force the workflow red
To reuse this automation in another repository:
## Extending The System
1. Copy `.forgejo/workflows/validate.yml`
2. Copy `.forgejo/workflows/project-report.yml`
3. Copy `tools/validate-code.sh`
4. Copy `tools/project-report.sh`
5. Create `reports/.gitkeep`
6. Keep the exclude list if the repository includes generated or dependency folders
7. Review language and framework detection rules for project-specific needs
To add more validators later:
## Notes
1. Update `tools/validate-code.sh`
2. Document the validator in `docs/VALIDATORS.md`
3. Update `docs/AUTOMATION.md` and `docs/WORKFLOWS.md` if workflow behavior changes
4. Update `docs/CODEX_INSTRUCTIONS.md`
- Reports are regenerated on every run.
- Validation skips checks gracefully when required tools are not installed.
- The automation is additive and does not modify customer source files.
To add more code documentation extraction later:
1. Update `tools/generate-code-docs.sh`
2. Document new reports in `docs/REPORTS.md`
3. Document workflow changes in `docs/WORKFLOWS.md`
4. Update `docs/CODEX_INSTRUCTIONS.md`

View file

@ -0,0 +1,52 @@
# Codex Instructions
## Repository Purpose
This repository is the master template for Runlevel Systems DevPlus1 customer debugging and analysis projects.
## Expected Workflow
1. Read this documentation set first.
2. Run or inspect validation automation.
3. Review generated reports before making assumptions about customer code.
4. Treat customer code errors as reportable debugging inputs, not workflow failures.
## Expected Reports
- `reports/errors.md`
- `reports/project-flow.md`
- `reports/file-summary.md`
- `reports/languages.md`
- `reports/summary.md`
- `reports/generated-files.md`
- `reports/api-reference.md`
- `reports/file-purpose.md`
- `reports/unity-scripts.md`
- `reports/call-map.md`
- `reports/code-docs-summary.md`
## Expected Validation Behavior
- Green workflow means automation completed.
- Red workflow means automation failed.
- Customer code errors do not fail workflows.
- Customer code errors are reported in `reports/errors.md`.
- Reports should always be generated whenever possible.
- New validators should be added through `VALIDATORS.md`.
- New workflows should be documented in `WORKFLOWS.md`.
- Code documentation reports are generated by `tools/generate-code-docs.sh`.
- The code documentation generator is read-only against customer source files.
## Recommended File Reading Order For Codex
1. `docs/CODEX_INSTRUCTIONS.md`
2. `docs/README.md`
3. `docs/ARCHITECTURE.md`
4. `docs/WORKFLOWS.md`
5. `docs/REPORTS.md`
6. `docs/VALIDATORS.md`
7. `docs/REPOSITORY_MAP.md`
## Maintenance Rule
Update this file whenever automation behavior changes. These docs are authoritative and should stay current as the repository evolves.

43
docs/README.md Normal file
View file

@ -0,0 +1,43 @@
# Customer-Test Documentation
## Purpose of Repository
This repository is the master template for Runlevel Systems DevPlus1 customer debugging and analysis projects.
Customers are expected to upload code that may already be broken. The repository automation is designed to inspect that code, generate reports, and keep the workflows green when the automation itself succeeds.
## Purpose of DevPlus1 Customer Debugging
DevPlus1 customer debugging projects need fast, repeatable automation that:
- validates customer code where supported
- reports errors clearly
- analyzes project structure
- generates documentation-friendly markdown output
- does not confuse customer code problems with automation failures
## Repository Goals
- Make Forgejo Actions useful for debugging projects.
- Generate reports on every push and pull request when possible.
- Keep customer code errors visible in logs and markdown.
- Reserve red workflows for automation failures only.
- Provide documentation that future Codex sessions can read first.
## High-Level Workflow
1. Customer code is pushed to the repository.
2. `validate.yml` runs validation and writes `reports/errors.md`.
3. `project-report.yml` generates structure and flow reports in `reports/`.
4. Forgejo logs print report summaries directly.
5. A developer or future Codex session reviews the reports before deeper work.
## Documentation Index
- [CODEX_INSTRUCTIONS.md](./CODEX_INSTRUCTIONS.md)
- [ARCHITECTURE.md](./ARCHITECTURE.md)
- [WORKFLOWS.md](./WORKFLOWS.md)
- [REPORTS.md](./REPORTS.md)
- [VALIDATORS.md](./VALIDATORS.md)
- [REPOSITORY_MAP.md](./REPOSITORY_MAP.md)
- [AUTOMATION.md](./AUTOMATION.md)

73
docs/REPORTS.md Normal file
View file

@ -0,0 +1,73 @@
# Reports
## `reports/errors.md`
- Purpose: validation report for customer code and repository files
- Generated by: `tools/validate-code.sh`
- Typical contents: validation summary, files checked, warnings, errors, detailed per-file results
## `reports/project-flow.md`
- Purpose: best-effort project flow analysis
- Generated by: `tools/project-report.sh`
- Typical contents: likely entry point, possible startup files, detected frameworks, detected databases, probable application flow
## `reports/file-summary.md`
- Purpose: repository structure summary
- Generated by: `tools/project-report.sh`
- Typical contents: top-level folders, file counts by type, largest source directories
## `reports/languages.md`
- Purpose: language inventory
- Generated by: `tools/project-report.sh`
- Typical contents: counts for supported source and config languages
## `reports/summary.md`
- Purpose: single high-level automation snapshot
- Generated by: `tools/project-report.sh`
- Typical contents: generation time, repo name, branch, commit, generated files, customer code status, overview, language counts, likely entry points, frameworks, databases
## `reports/generated-files.md`
- Purpose: explicit list of report files created or updated during the run
- Generated by: both scripts
- Typical contents: markdown list of generated report files
## `reports/customer-code-status.txt`
- Purpose: single-line machine-readable customer code status
- Generated by: `tools/validate-code.sh`
- Typical contents: `CUSTOMER CODE ERRORS FOUND` or `NO CUSTOMER CODE ERRORS FOUND`
## `reports/api-reference.md`
- Purpose: at-a-glance reference of detected functions, methods, classes, and public APIs
- Generated by: `tools/generate-code-docs.sh`
- Typical contents: file path, language, class, function/method name, parameters, return type, visibility, static/async markers, guessed purpose, parsing notes
## `reports/file-purpose.md`
- Purpose: file-by-file summary for quick customer project review
- Generated by: `tools/generate-code-docs.sh`
- Typical contents: file path, language, line count, detected classes, detected functions, imports/includes, possible purpose, likely role notes
## `reports/unity-scripts.md`
- Purpose: Unity-specific lifecycle and script role map
- Generated by: `tools/generate-code-docs.sh`
- Typical contents: MonoBehaviour/ScriptableObject classes, lifecycle methods, serialized/public fields, public methods, Unity API usage, possible script role
## `reports/call-map.md`
- Purpose: best-effort static map of possible entry points, imports/includes, function definitions, call references, and relationships
- Generated by: `tools/generate-code-docs.sh`
- Typical contents: possible entry points, detected imports, function definitions, possible calls, relationship guesses, limitations
## `reports/code-docs-summary.md`
- Purpose: concise summary of the code documentation generation run
- Generated by: `tools/generate-code-docs.sh`
- Typical contents: reports written, source files scanned, functions/methods detected, classes detected, Unity scripts detected, possible entry points, detected code types

36
docs/REPOSITORY_MAP.md Normal file
View file

@ -0,0 +1,36 @@
# Repository Map
## Top-Level Structure
- `.forgejo/workflows/`
Purpose: Forgejo Actions workflow definitions
- `tools/`
Purpose: validation and report-generation scripts
- `reports/`
Purpose: generated markdown and status outputs
- `tests/validation-samples/`
Purpose: intentional valid and broken sample files for validator behavior
- `docs/`
Purpose: authoritative repository documentation
## Scripts
- `tools/validate-code.sh`
Purpose: run validators, write `reports/errors.md`, and keep customer code errors non-fatal
- `tools/project-report.sh`
Purpose: generate structural and summary reports
- `tools/generate-code-docs.sh`
Purpose: generate API, file-purpose, Unity lifecycle, and call-map documentation reports
## Workflows
- `.forgejo/workflows/validate.yml`
Purpose: run validation and print the validation report into Forgejo logs
- `.forgejo/workflows/project-report.yml`
Purpose: generate project analysis reports and print useful summaries into Forgejo logs
- `.forgejo/workflows/code-docs.yml`
Purpose: generate code documentation reports and print useful summaries into Forgejo logs
## Customer Code
Customer source files may be broken on purpose. They are analyzed and reported on, not treated as workflow-breaking by default.

78
docs/VALIDATORS.md Normal file
View file

@ -0,0 +1,78 @@
# Validators
## PHP
- Tool used: `php`
- Command used: `php -l <file>`
- Requirements: PHP CLI installed
- Limitations: syntax only
## Python
- Tool used: `python3`
- Command used: `python3 -m py_compile <file>`
- Requirements: Python 3 installed
- Limitations: compile/syntax only
## JavaScript
- Tool used: `node`
- Command used: `node --check <file>`
- Requirements: Node.js installed
- Limitations: syntax only
## JSON
- Tool used: `jq`
- Command used: `jq empty <file>`
- Requirements: `jq` installed
- Limitations: structure validation only
## YAML
- Tool used: `yamllint`
- Command used: `yamllint -f parsable <file>`
- Requirements: `yamllint` installed
- Limitations: skipped when tool is unavailable
## Shell
- Tool used: `bash`
- Command used: `bash -n <file>`
- Requirements: Bash installed
- Limitations: parse check only
## Ruby
- Tool used: `ruby`
- Command used: `ruby -c <file>`
- Requirements: Ruby installed
- Limitations: syntax only
## Perl
- Tool used: `perl`
- Command used: `perl -c <file>`
- Requirements: Perl installed
- Limitations: compile/syntax only
## C#
- Tool used: `dotnet`
- Command used: `dotnet build <solution-or-project>`
- Requirements: `.sln` or `.csproj` plus .NET SDK
- Limitations: standalone `.cs` files are not validated directly; true validation requires a project or solution
## C/C++
- Tool used: `cmake`
- Command used: `cmake -S <repo> -B <temp-build-dir>`
- Requirements: `CMakeLists.txt` and `cmake`
- Limitations: configure validation only, not full compile unless extended later
## HTML
- Tool used: `tidy`
- Command used: `tidy -qe <file>`
- Requirements: `tidy` installed
- Limitations: skipped when tool is unavailable

103
docs/WORKFLOWS.md Normal file
View file

@ -0,0 +1,103 @@
# Workflows
## `validate.yml`
### Trigger
- `push`
- `pull_request`
### Input
- current repository contents
### Output
- `reports/errors.md`
- `reports/generated-files.md`
- `reports/customer-code-status.txt`
- Forgejo log summary
### Success Conditions
- `tools/validate-code.sh` completed
- `reports/errors.md` exists
### Failure Conditions
- validation script crashed
- `reports/errors.md` was not generated
### Important Behavior
Customer code errors do not fail the workflow.
## `project-report.yml`
### Trigger
- `push`
- `pull_request`
### Input
- current repository contents
- validation results when generated during the run
### Output
- `reports/project-flow.md`
- `reports/file-summary.md`
- `reports/languages.md`
- `reports/summary.md`
- `reports/generated-files.md`
- optional `reports/errors.md` excerpt in logs
### Success Conditions
- report scripts completed
- required report files exist
### Failure Conditions
- report script crashed
- required reports were not generated
### Important Behavior
This workflow remains green when customer code errors are detected, as long as report generation succeeds.
## `code-docs.yml`
### Trigger
- `push`
- `pull_request`
### Input
- current repository contents
### Output
- `reports/api-reference.md`
- `reports/file-purpose.md`
- `reports/unity-scripts.md`
- `reports/call-map.md`
- `reports/code-docs-summary.md`
- updated `reports/generated-files.md`
- Forgejo log summary
### Success Conditions
- `tools/generate-code-docs.sh` completed
- all required code documentation reports exist
### Failure Conditions
- documentation script crashed
- required code documentation reports were not generated
### Important Behavior
Customer code problems do not fail this workflow. The workflow is read-only against customer source code and only writes markdown reports.

77
reports/api-reference.md Normal file
View file

@ -0,0 +1,77 @@
# API Reference
Parsing is best-effort. Dynamic/runtime APIs and complex signatures may not be detected.
| File | Language | Class | Function/Method | Parameters | Return Type | Visibility | Static/Async | Guessed Purpose | Notes |
|---|---|---|---|---|---|---|---|---|---|
| `tests/validation-samples/csharp/broken.cs` | C# | ValidationSampleBroken | class declaration | `not applicable` | not applicable | not detected | not detected | Possible class or type definition. | Signature appears to be class-like. |
| `tests/validation-samples/csharp/valid.cs` | C# | ValidationSampleValid | class declaration | `not applicable` | not applicable | not detected | not detected | Possible class or type definition. | Signature appears to be class-like. |
| `tools/generate-code-docs.sh` | Shell | not detected | relative_path | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 43. |
| `tools/generate-code-docs.sh` | Shell | not detected | record_generated_file | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 49. |
| `tools/generate-code-docs.sh` | Shell | not detected | append_detected_type | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 61. |
| `tools/generate-code-docs.sh` | Shell | not detected | collect_source_files | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 72. |
| `tools/generate-code-docs.sh` | Shell | not detected | language_for_file | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 91. |
| `tools/generate-code-docs.sh` | Shell | not detected | line_count | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 109. |
| `tools/generate-code-docs.sh` | Shell | not detected | join_names | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 113. |
| `tools/generate-code-docs.sh` | Shell | not detected | guess_purpose | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 117. |
| `tools/generate-code-docs.sh` | Shell | not detected | is_entry_point | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 160. |
| `tools/generate-code-docs.sh` | Shell | not detected | extract_classes | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 176. |
| `tools/generate-code-docs.sh` | Shell | not detected | extract_functions | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 203. |
| `tools/generate-code-docs.sh` | Shell | not detected | extract_imports | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 236. |
| `tools/generate-code-docs.sh` | Shell | not detected | function_name_from_line | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 269. |
| `tools/generate-code-docs.sh` | Shell | not detected | return_type_from_line | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 298. |
| `tools/generate-code-docs.sh` | Shell | not detected | parameters_from_line | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 319. |
| `tools/generate-code-docs.sh` | Shell | not detected | visibility_from_line | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 324. |
| `tools/generate-code-docs.sh` | Shell | not detected | static_async_from_line | `` | not detected | not detected | static async | Purpose guessed from name only | Detected function near line 337. |
| `tools/generate-code-docs.sh` | Shell | not detected | guess_function_purpose | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 349. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_api_reference | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 367. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_file_purpose | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 408. |
| `tools/generate-code-docs.sh` | Shell | not detected | unity_role_guess | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 434. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_unity_scripts | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 464. |
| `tools/generate-code-docs.sh` | Shell | not detected | extract_called_names | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 511. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_call_map | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 516. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_generated_files_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 577. |
| `tools/generate-code-docs.sh` | Shell | not detected | write_summary | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 598. |
| `tools/generate-code-docs.sh` | Shell | not detected | print_summary | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 628. |
| `tools/generate-code-docs.sh` | Shell | not detected | main | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 653. |
| `tools/project-report.sh` | Shell | not detected | have_tool | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 53. |
| `tools/project-report.sh` | Shell | not detected | record_generated_file | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 57. |
| `tools/project-report.sh` | Shell | not detected | relative_path | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 71. |
| `tools/project-report.sh` | Shell | not detected | ensure_report_dir | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 77. |
| `tools/project-report.sh` | Shell | not detected | collect_files | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 81. |
| `tools/project-report.sh` | Shell | not detected | search_tree | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 98. |
| `tools/project-report.sh` | Shell | not detected | append_unique | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 126. |
| `tools/project-report.sh` | Shell | not detected | detect_entry_or_startup | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 140. |
| `tools/project-report.sh` | Shell | not detected | detect_config_files | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 158. |
| `tools/project-report.sh` | Shell | not detected | classify_language | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 186. |
| `tools/project-report.sh` | Shell | not detected | detect_frameworks | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 205. |
| `tools/project-report.sh` | Shell | not detected | detect_databases | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 224. |
| `tools/project-report.sh` | Shell | not detected | read_customer_status | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 236. |
| `tools/project-report.sh` | Shell | not detected | detect_repo_name | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 246. |
| `tools/project-report.sh` | Shell | not detected | detect_branch_name | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 250. |
| `tools/project-report.sh` | Shell | not detected | detect_commit_hash | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 258. |
| `tools/project-report.sh` | Shell | not detected | write_languages_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 266. |
| `tools/project-report.sh` | Shell | not detected | write_file_summary_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 289. |
| `tools/project-report.sh` | Shell | not detected | write_project_flow_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 332. |
| `tools/project-report.sh` | Shell | not detected | write_summary_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 423. |
| `tools/project-report.sh` | Shell | not detected | write_generated_files_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 514. |
| `tools/project-report.sh` | Shell | not detected | main | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 540. |
| `tools/validate-code.sh` | Shell | not detected | cleanup | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 31. |
| `tools/validate-code.sh` | Shell | not detected | record_generated_file | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 39. |
| `tools/validate-code.sh` | Shell | not detected | escape_md | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 53. |
| `tools/validate-code.sh` | Shell | not detected | have_tool | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 57. |
| `tools/validate-code.sh` | Shell | not detected | ensure_report_dir | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 61. |
| `tools/validate-code.sh` | Shell | not detected | add_warning | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 68. |
| `tools/validate-code.sh` | Shell | not detected | add_error | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 74. |
| `tools/validate-code.sh` | Shell | not detected | add_ok | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 80. |
| `tools/validate-code.sh` | Shell | not detected | relative_path | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 85. |
| `tools/validate-code.sh` | Shell | not detected | join_by | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 91. |
| `tools/validate-code.sh` | Shell | not detected | collect_files | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 109. |
| `tools/validate-code.sh` | Shell | not detected | run_check | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 126. |
| `tools/validate-code.sh` | Shell | not detected | run_repo_build_checks | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 152. |
| `tools/validate-code.sh` | Shell | not detected | validate_file | `` | not detected | not detected | not detected | Likely validates data or state | Detected function near line 204. |
| `tools/validate-code.sh` | Shell | not detected | write_generated_files_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 283. |
| `tools/validate-code.sh` | Shell | not detected | write_status_file | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 299. |
| `tools/validate-code.sh` | Shell | not detected | write_error_report | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 309. |
| `tools/validate-code.sh` | Shell | not detected | print_terminal_summary | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 332. |
| `tools/validate-code.sh` | Shell | not detected | main | `` | not detected | not detected | not detected | Purpose guessed from name only | Detected function near line 351. |

35
reports/call-map.md Normal file
View file

@ -0,0 +1,35 @@
# Call Map
Best-effort static scan. Dynamic/runtime calls may not be detected.
## Possible Entry Points
- `index.php`
## Detected Imports/Includes
## Function Definitions
- `tools/generate-code-docs.sh`: relative_pathrecord_generated_fileappend_detected_typecollect_source_fileslanguage_for_fileline_countjoin_namesguess_purposeis_entry_pointextract_classesextract_functionsextract_importsfunction_name_from_linereturn_type_from_lineparameters_from_linevisibility_from_linestatic_async_from_lineguess_function_purposewrite_api_referencewrite_file_purposeunity_role_guesswrite_unity_scriptsextract_called_nameswrite_call_mapwrite_generated_files_reportwrite_summaryprint_summarymain
- `tools/project-report.sh`: have_toolrecord_generated_filerelative_pathensure_report_dircollect_filessearch_treeappend_uniquedetect_entry_or_startupdetect_config_filesclassify_languagedetect_frameworksdetect_databasesread_customer_statusdetect_repo_namedetect_branch_namedetect_commit_hashwrite_languages_reportwrite_file_summary_reportwrite_project_flow_reportwrite_summary_reportwrite_generated_files_reportmain
- `tools/validate-code.sh`: cleanuprecord_generated_fileescape_mdhave_toolensure_report_diradd_warningadd_erroradd_okrelative_pathjoin_bycollect_filesrun_checkrun_repo_build_checksvalidate_filewrite_generated_files_reportwrite_status_filewrite_error_reportprint_terminal_summarymain
## Function Call References
- `tests/validation-samples/csharp/broken.cs`: possible calls to Answer
- `tests/validation-samples/csharp/valid.cs`: possible calls to Answer
- `tests/validation-samples/javascript/broken.js`: possible calls to log
- `tests/validation-samples/javascript/valid.js`: possible calls to log
- `tools/generate-code-docs.sh`: possible calls to relative_path, record_generated_file, append_detected_type, collect_source_files, language_for_file, line_count, join_names, gsub, guess_purpose, is_entry_point, extract_classes, extract_functions, extract_imports, require, function_name_from_line, return_type_from_line, parameters_from_line, visibility_from_line, static_async_from_line, guess_function_purpose, write_api_reference, write_file_purpose, unity_role_guess, write_unity_scripts, b, extract_called_names, write_call_map, write_generated_files_report, write_summary, print_summary, main
- `tools/project-report.sh`: possible calls to have_tool, record_generated_file, relative_path, ensure_report_dir, collect_files, search_tree, append_unique, detect_entry_or_startup, detect_config_files, classify_language, detect_frameworks, detect_databases, read_customer_status, detect_repo_name, detect_branch_name, detect_commit_hash, write_languages_report, write_file_summary_report, write_project_flow_report, write_summary_report, write_generated_files_report, main
- `tools/validate-code.sh`: possible calls to cleanup, record_generated_file, escape_md, have_tool, ensure_report_dir, add_warning, add_error, add_ok, relative_path, join_by, collect_files, run_check, run_repo_build_checks, validate_file, write_generated_files_report, write_status_file, write_error_report, print_terminal_summary, main
## Possible Call Relationships
## Limitations
- Best-effort static scan only.
- Runtime reflection, framework routing, dependency injection, dynamic imports, and generated code may not be detected.
- Unity lifecycle methods are entry-like runtime hooks, not direct command-line entry points.

View file

@ -0,0 +1,24 @@
# Code Documentation Summary
===== CODE DOCUMENTATION SUMMARY =====
Reports written:
- reports/api-reference.md
- reports/file-purpose.md
- reports/unity-scripts.md
- reports/call-map.md
- reports/code-docs-summary.md
Detected:
- Source files scanned: 12
- Functions/methods detected: 69
- Classes detected: 2
- Unity scripts detected: 0
- Possible entry points detected: 1
Detected code types:
- PHP
- C#
- JavaScript
- Python
- Shell

View file

@ -0,0 +1 @@
CUSTOMER CODE ERRORS FOUND

View file

@ -1,12 +1,20 @@
# Validation Summary
- Files Checked: 3
CUSTOMER CODE ERRORS FOUND
- Files Checked: 9
- Warnings: 1
- Errors: 1
- Errors: 3
## Detailed Results
- OK: bash -n passed: tools/validate-code.sh
- OK: bash -n passed: tools/project-report.sh
- Warning: yamllint not installed; skipping validate.yml
- Error: php -l failed: index.php \| Errors parsing /home/dev/Documents/customer-test/index.php \| PHP Parse error: syntax error, unexpected end of file, expecting "," or ";" in /home/dev/Documents/customer-test/index.php on line 5
- Error: php -l failed: tests/validation-samples/php/broken.php \| Errors parsing /home/dev/Documents/customer-test/tests/validation-samples/php/broken.php \| PHP Parse error: syntax error, unexpected end of file, expecting "," or ";" in /home/dev/Documents/customer-test/tests/validation-samples/php/broken.php on line 3
- OK: php -l passed: tests/validation-samples/php/valid.php
- OK: python3 -m py_compile passed: tests/validation-samples/python/valid.py
- Error: python3 -m py_compile failed: tests/validation-samples/python/broken.py \| File "/home/dev/Documents/customer-test/tests/validation-samples/python/broken.py", line 1 print("broken python sample" ^ SyntaxError: '(' was never closed
- OK: node --check passed: tests/validation-samples/javascript/valid.js
- Error: node --check failed: tests/validation-samples/javascript/broken.js \| /home/dev/Documents/customer-test/tests/validation-samples/javascript/broken.js:1 console.log("broken javascript sample" ^^^^^^^^^^^^^^^^^^^^^^^^^^ SyntaxError: missing ) after argument list at internalCompileFunction (node:internal/vm:73:18) at wrapSafe (node:internal/modules/cjs/loader:1274:20) at node:internal/main/check_syntax:84:41 at loadESM (node:internal/process/esm_loader:34:13) at checkSyntax (node:internal/main/check_syntax:84:21) Node.js v18.19.1
- OK: php -l passed: index.php

18
reports/file-purpose.md Normal file
View file

@ -0,0 +1,18 @@
# File Purpose Report
This report uses cautious, best-effort guesses based on filenames, folders, imports, classes, and functions.
| File | Language/Type | Lines | Detected Classes | Detected Functions/Methods | Imports/Includes/Usings | Possible Purpose | Notes |
|---|---|---:|---|---|---|---|---|
| `index.php` | PHP | 4 | none detected | none detected | none detected | Likely entry point | Appears to be best-effort only. |
| `tests/validation-samples/csharp/broken.cs` | C# | 7 | ValidationSampleBroken | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/csharp/valid.cs` | C# | 7 | ValidationSampleValid | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/javascript/broken.js` | JavaScript | 1 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/javascript/valid.js` | JavaScript | 1 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/php/broken.php` | PHP | 2 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/php/valid.php` | PHP | 2 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/python/broken.py` | Python | 1 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tests/validation-samples/python/valid.py` | Python | 1 | none detected | none detected | none detected | Likely test/sample file | Appears to be best-effort only. |
| `tools/generate-code-docs.sh` | Shell | 675 | none detected | relative_pathrecord_generated_fileappend_detected_typecollect_source_fileslanguage_for_fileline_countjoin_namesguess_purposeis_entry_pointextract_classesextract_functionsextract_importsfunction_name_from_linereturn_type_from_lineparameters_from_linevisibility_from_linestatic_async_from_lineguess_function_purposewrite_api_referencewrite_file_purposeunity_role_guesswrite_unity_scriptsextract_called_nameswrite_call_mapwrite_generated_files_reportwrite_summaryprint_summarymain | none detected | Possible automation or launch script | Appears to be best-effort only. |
| `tools/project-report.sh` | Shell | 586 | none detected | have_toolrecord_generated_filerelative_pathensure_report_dircollect_filessearch_treeappend_uniquedetect_entry_or_startupdetect_config_filesclassify_languagedetect_frameworksdetect_databasesread_customer_statusdetect_repo_namedetect_branch_namedetect_commit_hashwrite_languages_reportwrite_file_summary_reportwrite_project_flow_reportwrite_summary_reportwrite_generated_files_reportmain | none detected | Possible automation or launch script | Appears to be best-effort only. |
| `tools/validate-code.sh` | Shell | 372 | none detected | cleanuprecord_generated_fileescape_mdhave_toolensure_report_diradd_warningadd_erroradd_okrelative_pathjoin_bycollect_filesrun_checkrun_repo_build_checksvalidate_filewrite_generated_files_reportwrite_status_filewrite_error_reportprint_terminal_summarymain | none detected | Possible automation or launch script | Appears to be best-effort only. |

35
reports/file-summary.md Normal file
View file

@ -0,0 +1,35 @@
# File Summary
## Top-Level Folders
- .forgejo
- docs
- reports
- tests
- tools
## File Counts By Type
| Type | Count |
|---|---:|
| PHP | 3 |
| JavaScript | 2 |
| TypeScript | 0 |
| Python | 2 |
| C# | 2 |
| C++ | 0 |
| Ruby | 0 |
| Perl | 0 |
| HTML | 0 |
| CSS | 0 |
| Shell | 2 |
| JSON | 0 |
| YAML | 1 |
## Largest Source Directories
- 9 files: tests/validation-samples
- 9 files: tests
- 8 files: reports
- 8 files: docs
- 2 files: tools

View file

@ -0,0 +1,14 @@
# Generated Files
- reports/api-reference.md
- reports/file-purpose.md
- reports/unity-scripts.md
- reports/call-map.md
- reports/code-docs-summary.md
- reports/languages.md
- reports/file-summary.md
- reports/project-flow.md
- reports/customer-code-status.txt
- reports/errors.md
- reports/summary.md
- reports/generated-files.md

17
reports/languages.md Normal file
View file

@ -0,0 +1,17 @@
# Languages
| Language | Count |
|---|---:|
| PHP | 3 |
| JavaScript | 2 |
| TypeScript | 0 |
| Python | 2 |
| C# | 2 |
| C++ | 0 |
| Ruby | 0 |
| Perl | 0 |
| HTML | 0 |
| CSS | 0 |
| Shell | 2 |
| JSON | 0 |
| YAML | 1 |

41
reports/project-flow.md Normal file
View file

@ -0,0 +1,41 @@
# Project Flow Report
## Likely Entry Point
- Likely entry point: index.php
## Possible Startup Files
- Possible startup file: index.php
## Detected Frameworks
- No supported framework markers were detected.
## Detected Databases
- Possible database usage: MySQL
- Possible database usage: MariaDB
- Possible database usage: PostgreSQL
- Possible database usage: SQLite
- Possible database usage: MongoDB
## Configuration Files
- No .env files detected.
- No Docker-related files detected.
- No obvious database config files detected.
## Important Files To Review
- index.php
- validate.yml
## Probable Application Flow
This is a best-effort analysis only.
- Probable application flow starts near `index.php`.
- Likely supporting behavior is configured through nearby config files, environment files, or workflow files.
- If framework markers are present, startup and routing probably follow that framework's conventions.
- If no framework markers are present, the repository likely uses a simpler file-based startup flow.

63
reports/summary.md Normal file
View file

@ -0,0 +1,63 @@
# Automation Summary
- Generated: 2026-06-15 11:34:49 CDT
- Repository: customer-test
- Branch: main
- Commit: 9042333
- Customer code status: CUSTOMER CODE ERRORS FOUND
## Files Created Or Updated By This Run
- reports/languages.md
- reports/file-summary.md
- reports/project-flow.md
- reports/customer-code-status.txt
- reports/errors.md
- reports/summary.md
- reports/generated-files.md
## Short Project Overview
This repository is being analyzed as a customer debugging project. The automation generates validation and structure reports without treating customer code errors as workflow failures.
## Language Counts
- PHP: 3
- JavaScript: 2
- TypeScript: 0
- Python: 2
- C#: 2
- C++: 0
- Ruby: 0
- Perl: 0
- HTML: 0
- CSS: 0
- Shell: 2
- JSON: 0
- YAML: 1
## Likely Entry Points
- index.php
## Detected Frameworks
- None detected
## Detected Databases
- MySQL
- MariaDB
- PostgreSQL
- SQLite
- MongoDB
## Report Files Generated
- reports/languages.md
- reports/file-summary.md
- reports/project-flow.md
- reports/customer-code-status.txt
- reports/errors.md
- reports/summary.md
- reports/generated-files.md

5
reports/unity-scripts.md Normal file
View file

@ -0,0 +1,5 @@
# Unity Script Lifecycle Map
This report scans C# files for MonoBehaviour, ScriptableObject, common Unity lifecycle methods, serialized fields, and Unity API usage.
No Unity MonoBehaviour or ScriptableObject scripts detected.

View file

@ -0,0 +1,7 @@
public class ValidationSampleBroken
{
public int Answer()
{
return 42
}
}

View file

@ -0,0 +1,7 @@
public class ValidationSampleValid
{
public int Answer()
{
return 42;
}
}

View file

@ -0,0 +1 @@
console.log("broken javascript sample"

View file

@ -0,0 +1 @@
console.log("valid javascript sample");

View file

@ -0,0 +1,2 @@
<?php
echo "broken php sample"

View file

@ -0,0 +1,2 @@
<?php
echo "valid php sample";

View file

@ -0,0 +1 @@
print("broken python sample"

View file

@ -0,0 +1 @@
print("valid python sample")

675
tools/generate-code-docs.sh Normal file
View file

@ -0,0 +1,675 @@
#!/usr/bin/env bash
set -u -o pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPORT_DIR="$ROOT_DIR/reports"
API_REPORT="$REPORT_DIR/api-reference.md"
FILE_PURPOSE_REPORT="$REPORT_DIR/file-purpose.md"
UNITY_REPORT="$REPORT_DIR/unity-scripts.md"
CALL_MAP_REPORT="$REPORT_DIR/call-map.md"
SUMMARY_REPORT="$REPORT_DIR/code-docs-summary.md"
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
SOURCE_FILES=()
DETECTED_CODE_TYPES=()
GENERATED_FILES=()
SOURCE_COUNT=0
FUNCTION_COUNT=0
CLASS_COUNT=0
UNITY_SCRIPT_COUNT=0
ENTRY_POINT_COUNT=0
EXCLUDES=(
".git"
".forgejo"
"node_modules"
"vendor"
"Library"
"Temp"
"Logs"
"obj"
"bin"
".vscode"
".cache"
)
mkdir -p "$REPORT_DIR" || {
printf 'Failed to create reports directory: %s\n' "$REPORT_DIR" >&2
exit 1
}
relative_path() {
local path="$1"
path="${path#"$ROOT_DIR"/}"
printf '%s' "$path"
}
record_generated_file() {
local file="$1"
local rel="${file#"$ROOT_DIR"/}"
local existing
for existing in "${GENERATED_FILES[@]}"; do
[[ "$existing" == "$rel" ]] && return
done
GENERATED_FILES+=("$rel")
}
append_detected_type() {
local type="$1"
local existing
for existing in "${DETECTED_CODE_TYPES[@]}"; do
[[ "$existing" == "$type" ]] && return
done
DETECTED_CODE_TYPES+=("$type")
}
collect_source_files() {
local find_args=("$ROOT_DIR")
local exclude
find_args+=("(")
for exclude in "${EXCLUDES[@]}"; do
find_args+=(-name "$exclude" -o)
done
unset 'find_args[${#find_args[@]}-1]'
find_args+=(")" -prune -o)
find_args+=("(")
find_args+=(-name '*.php' -o -name '*.py' -o -name '*.js' -o -name '*.ts' -o -name '*.cs' -o -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' -o -name '*.rb' -o -name '*.pl' -o -name '*.pm' -o -name '*.sh' -o -name '*.html' -o -name '*.htm' -o -name '*.css')
find_args+=(")" -type f -print0)
while IFS= read -r -d '' file; do
SOURCE_FILES+=("$file")
done < <(find "${find_args[@]}" | sort -z)
}
language_for_file() {
local file="$1"
case "$file" in
*.php) printf 'PHP' ;;
*.py) printf 'Python' ;;
*.js) printf 'JavaScript' ;;
*.ts) printf 'TypeScript' ;;
*.cs) printf 'C#' ;;
*.c|*.cpp|*.h|*.hpp) printf 'C/C++' ;;
*.rb) printf 'Ruby' ;;
*.pl|*.pm) printf 'Perl' ;;
*.sh) printf 'Shell' ;;
*.html|*.htm) printf 'HTML' ;;
*.css) printf 'CSS' ;;
*) printf 'Unknown' ;;
esac
}
line_count() {
wc -l <"$1" | tr -d ' '
}
join_names() {
awk 'NF {gsub(/^[[:space:]]+|[[:space:]]+$/, ""); if (!seen[$0]++) { if (count++) printf ", "; printf "%s", $0 }}'
}
guess_purpose() {
local file="$1"
local rel base lower language
rel="$(relative_path "$file")"
base="$(basename "$file")"
lower="$(printf '%s' "$rel" | tr '[:upper:]' '[:lower:]')"
language="$(language_for_file "$file")"
case "$base" in
index.php|index.html|index.htm|main.py|app.py|server.py|server.js|app.js|index.js|Program.cs|Main.cs|main.cpp|main.c)
printf 'Likely entry point'
return
;;
esac
case "$lower" in
*config*|*settings*|*.env*) printf 'Possible configuration file' ;;
*controller*) printf 'Likely controller or request handler' ;;
*model*) printf 'Likely model or data object' ;;
*view*|*template*) printf 'Likely view/template' ;;
*test*|*sample*) printf 'Likely test/sample file' ;;
*database*|*db*) printf 'Possible database access or database configuration' ;;
*api*) printf 'Possible API endpoint or API helper' ;;
*util*|*helper*) printf 'Likely utility/helper' ;;
*manager*) printf 'Likely manager/controller script' ;;
*player*) printf 'Possible player controller or player state script' ;;
*enemy*) printf 'Possible enemy AI or enemy state script' ;;
*health*|*damage*) printf 'Possible health/damage system' ;;
*)
if [[ "$language" == "HTML" ]]; then
printf 'Likely page/template'
elif [[ "$language" == "CSS" ]]; then
printf 'Likely stylesheet'
elif [[ "$language" == "Shell" ]]; then
printf 'Possible automation or launch script'
else
printf 'Possible source module'
fi
;;
esac
}
is_entry_point() {
local base
base="$(basename "$1")"
case "$base" in
index.php|index.html|index.htm|main.py|app.py|server.py|server.js|app.js|index.js|Program.cs|Main.cs|main.cpp|main.c)
return 0
;;
esac
if [[ "$1" == *.cs ]] && grep -Eq 'static[[:space:]]+.*Main[[:space:]]*\(' "$1"; then
return 0
fi
return 1
}
extract_classes() {
local file="$1"
local language
language="$(language_for_file "$file")"
case "$language" in
PHP)
grep -Eoh '^[[:space:]]*(abstract[[:space:]]+|final[[:space:]]+)?class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*|^[[:space:]]*interface[[:space:]]+[A-Za-z_][A-Za-z0-9_]*|^[[:space:]]*trait[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $NF}'
;;
Python)
grep -Eoh '^[[:space:]]*class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $2}'
;;
JavaScript|TypeScript)
grep -Eoh '^[[:space:]]*(export[[:space:]]+default[[:space:]]+|export[[:space:]]+)?class[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*' "$file" | awk '{print $NF}'
;;
C#)
grep -Eoh '^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(abstract|sealed|static|partial)?[[:space:]]*(class|struct|interface)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $NF}'
;;
C/C++)
grep -Eoh '^[[:space:]]*(class|struct)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $2}'
;;
Ruby)
grep -Eoh '^[[:space:]]*(class|module)[[:space:]]+[A-Za-z_:][A-Za-z0-9_:]*' "$file" | awk '{print $2}'
;;
esac
}
extract_functions() {
local file="$1"
local language
language="$(language_for_file "$file")"
case "$language" in
PHP)
grep -En '^[[:space:]]*((public|protected|private|static|final|abstract)[[:space:]]+)*function[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" || true
;;
Python)
grep -En '^[[:space:]]*(async[[:space:]]+)?def[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" || true
;;
JavaScript|TypeScript)
grep -En '^[[:space:]]*(export[[:space:]]+)?(async[[:space:]]+)?function[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*\(|^[[:space:]]*(export[[:space:]]+)?(const|let|var)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*=[[:space:]]*(async[[:space:]]*)?(\([^)]*\)|[A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*=>|^[[:space:]]*(public|private|protected)?[[:space:]]*(async[[:space:]]+)?[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*\([^)]*\)[[:space:]]*\{' "$file" || true
;;
C#)
grep -En '^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(static[[:space:]]+)?(async[[:space:]]+)?[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\([^;]*\)[[:space:]]*(\{|=>)' "$file" || true
;;
C/C++)
grep -En '^[[:space:]]*[A-Za-z_][A-Za-z0-9_:<>\*&[:space:]]+[A-Za-z_][A-Za-z0-9_:]*[[:space:]]*\([^;]*\)[[:space:]]*(\{|$)' "$file" || true
;;
Ruby)
grep -En '^[[:space:]]*def[[:space:]]+[A-Za-z_][A-Za-z0-9_!?=]*' "$file" || true
;;
Perl)
grep -En '^[[:space:]]*sub[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" || true
;;
Shell)
grep -En '^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(\)|function[[:space:]]+[A-Za-z_][A-Za-z0-9_]*)' "$file" || true
;;
esac
}
extract_imports() {
local file="$1"
local language
language="$(language_for_file "$file")"
case "$language" in
PHP)
grep -Eoh '^[[:space:]]*(include|include_once|require|require_once)[[:space:]]*\(?[^;]+|^[[:space:]]*use[[:space:]]+[^;]+' "$file" || true
;;
Python)
grep -Eoh '^[[:space:]]*(import|from)[[:space:]].+' "$file" || true
;;
JavaScript|TypeScript)
grep -Eoh '^[[:space:]]*import[[:space:]].+|require[[:space:]]*\([^)]+\)' "$file" || true
;;
C#)
grep -Eoh '^[[:space:]]*using[[:space:]][^;]+' "$file" || true
;;
C/C++)
grep -Eoh '^[[:space:]]*#include[[:space:]]+[<"][^>"]+[>"]' "$file" || true
;;
Ruby)
grep -Eoh '^[[:space:]]*require(_relative)?[[:space:]].+' "$file" || true
;;
Perl)
grep -Eoh '^[[:space:]]*(use|require)[[:space:]].+' "$file" || true
;;
Shell)
grep -Eoh '^[[:space:]]*(source|\.)[[:space:]].+' "$file" || true
;;
esac
}
function_name_from_line() {
local language="$1"
local line="$2"
case "$language" in
PHP)
printf '%s' "$line" | sed -E 's/.*function[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(.*/\1/'
;;
Python)
printf '%s' "$line" | sed -E 's/.*def[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(.*/\1/'
;;
JavaScript|TypeScript)
printf '%s' "$line" | sed -E 's/.*function[[:space:]]+([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*\(.*/\1/; s/.*(const|let|var)[[:space:]]+([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*=.*/\2/; s/^[[:space:]]*(public|private|protected)?[[:space:]]*(async[[:space:]]+)?([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*\(.*/\3/'
;;
C#|C/C++)
printf '%s' "$line" | sed -E 's/.*[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\([^;]*\).*/\1/'
;;
Ruby)
printf '%s' "$line" | sed -E 's/.*def[[:space:]]+([A-Za-z_][A-Za-z0-9_!?=]*).*/\1/'
;;
Perl)
printf '%s' "$line" | sed -E 's/.*sub[[:space:]]+([A-Za-z_][A-Za-z0-9_]*).*/\1/'
;;
Shell)
printf '%s' "$line" | sed -E 's/^[[:space:]]*function[[:space:]]+([A-Za-z_][A-Za-z0-9_]*).*/\1/; s/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(\).*/\1/'
;;
esac
}
return_type_from_line() {
local language="$1"
local line="$2"
case "$language" in
C#)
printf '%s' "$line" | sed -E 's/^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(static[[:space:]]+)?(async[[:space:]]+)?([A-Za-z_][A-Za-z0-9_<>,\[\]\?]*)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(.*/\4/'
;;
TypeScript)
if [[ "$line" =~ \)[[:space:]]*:[[:space:]]*([^=\{]+) ]]; then
printf '%s' "${BASH_REMATCH[1]}" | sed 's/[[:space:]]*$//'
else
printf 'not detected'
fi
;;
*)
printf 'not detected'
;;
esac
}
parameters_from_line() {
local line="$1"
printf '%s' "$line" | sed -E 's/^[^(]*\(([^)]*)\).*/\1/; t; s/.*/not detected/'
}
visibility_from_line() {
local line="$1"
if [[ "$line" =~ public ]]; then
printf 'public'
elif [[ "$line" =~ protected ]]; then
printf 'protected'
elif [[ "$line" =~ private ]]; then
printf 'private'
else
printf 'not detected'
fi
}
static_async_from_line() {
local line="$1"
local flags=()
[[ "$line" =~ static ]] && flags+=("static")
[[ "$line" =~ async ]] && flags+=("async")
if ((${#flags[@]} == 0)); then
printf 'not detected'
else
printf '%s' "${flags[*]}"
fi
}
guess_function_purpose() {
local name="$1"
local lower
lower="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')"
case "$lower" in
get*|fetch*|load*) printf 'Likely reads or retrieves data' ;;
set*|save*|store*) printf 'Likely writes or updates data' ;;
render*|show*|display*) printf 'Likely renders or displays output' ;;
handle*|process*) printf 'Likely handles a request, event, or workflow' ;;
validate*|check*) printf 'Likely validates data or state' ;;
start*|init*|awake) printf 'Likely initialization or startup behavior' ;;
update|fixedupdate|lateupdate) printf 'Unity runtime update hook' ;;
destroy*|delete*|remove*) printf 'Likely deletion or cleanup behavior' ;;
*) printf 'Purpose guessed from name only' ;;
esac
}
write_api_reference() {
{
printf '# API Reference\n\n'
printf 'Parsing is best-effort. Dynamic/runtime APIs and complex signatures may not be detected.\n\n'
printf '| File | Language | Class | Function/Method | Parameters | Return Type | Visibility | Static/Async | Guessed Purpose | Notes |\n'
printf '|---|---|---|---|---|---|---|---|---|---|\n'
local file language rel classes functions line_no signature name params return_type visibility flags class_list purpose
for file in "${SOURCE_FILES[@]}"; do
language="$(language_for_file "$file")"
rel="$(relative_path "$file")"
class_list="$(extract_classes "$file" | join_names)"
[[ -z "$class_list" ]] && class_list="not detected"
while IFS= read -r fn_line; do
[[ -z "$fn_line" ]] && continue
line_no="${fn_line%%:*}"
signature="${fn_line#*:}"
name="$(function_name_from_line "$language" "$signature")"
[[ -z "$name" || "$name" == "$signature" ]] && name="not detected"
params="$(parameters_from_line "$signature")"
return_type="$(return_type_from_line "$language" "$signature")"
visibility="$(visibility_from_line "$signature")"
flags="$(static_async_from_line "$signature")"
purpose="$(guess_function_purpose "$name")"
FUNCTION_COUNT=$((FUNCTION_COUNT + 1))
printf '| `%s` | %s | %s | %s | `%s` | %s | %s | %s | %s | Detected function near line %s. |\n' "$rel" "$language" "$class_list" "$name" "$params" "$return_type" "$visibility" "$flags" "$purpose" "$line_no"
done < <(extract_functions "$file")
local class_name
while IFS= read -r class_name; do
[[ -z "$class_name" ]] && continue
CLASS_COUNT=$((CLASS_COUNT + 1))
printf '| `%s` | %s | %s | class declaration | `not applicable` | not applicable | not detected | not detected | Possible class or type definition. | Signature appears to be class-like. |\n' "$rel" "$language" "$class_name"
done < <(extract_classes "$file")
done
} >"$API_REPORT"
record_generated_file "$API_REPORT"
}
write_file_purpose() {
{
printf '# File Purpose Report\n\n'
printf 'This report uses cautious, best-effort guesses based on filenames, folders, imports, classes, and functions.\n\n'
printf '| File | Language/Type | Lines | Detected Classes | Detected Functions/Methods | Imports/Includes/Usings | Possible Purpose | Notes |\n'
printf '|---|---|---:|---|---|---|---|---|\n'
local file rel language lines classes functions imports purpose
for file in "${SOURCE_FILES[@]}"; do
rel="$(relative_path "$file")"
language="$(language_for_file "$file")"
lines="$(line_count "$file")"
classes="$(extract_classes "$file" | join_names)"
functions="$(extract_functions "$file" | sed -E 's/^[0-9]+://' | while IFS= read -r line; do function_name_from_line "$language" "$line"; done | join_names)"
imports="$(extract_imports "$file" | join_names)"
purpose="$(guess_purpose "$file")"
[[ -z "$classes" ]] && classes="none detected"
[[ -z "$functions" ]] && functions="none detected"
[[ -z "$imports" ]] && imports="none detected"
printf '| `%s` | %s | %s | %s | %s | %s | %s | Appears to be best-effort only. |\n' "$rel" "$language" "$lines" "$classes" "$functions" "$imports" "$purpose"
done
} >"$FILE_PURPOSE_REPORT"
record_generated_file "$FILE_PURPOSE_REPORT"
}
unity_role_guess() {
local file="$1"
local content_lower
content_lower="$(tr '[:upper:]' '[:lower:]' <"$file")"
if grep -qi 'gamemanager\|game manager' <<<"$content_lower"; then
printf 'Game manager'
elif grep -qi 'player' <<<"$content_lower"; then
printf 'Player controller'
elif grep -qi 'enemy\|ai' <<<"$content_lower"; then
printf 'Enemy AI'
elif grep -qi 'spawner\|spawn' <<<"$content_lower"; then
printf 'Spawner'
elif grep -qi 'health\|damage' <<<"$content_lower"; then
printf 'Health/damage system'
elif grep -qi 'camera' <<<"$content_lower"; then
printf 'Camera controller'
elif grep -qi 'audio' <<<"$content_lower"; then
printf 'Audio manager'
elif grep -qi 'scene' <<<"$content_lower"; then
printf 'Scene loader'
elif grep -qi 'inventory' <<<"$content_lower"; then
printf 'Inventory system'
elif grep -qi 'ui\|canvas\|textmesh' <<<"$content_lower"; then
printf 'UI controller'
else
printf 'Utility script'
fi
}
write_unity_scripts() {
local found=0
{
printf '# Unity Script Lifecycle Map\n\n'
printf 'This report scans C# files for MonoBehaviour, ScriptableObject, common Unity lifecycle methods, serialized fields, and Unity API usage.\n\n'
local file rel classes base_classes lifecycle fields public_methods unity_api role class_name
for file in "${SOURCE_FILES[@]}"; do
[[ "$file" != *.cs ]] && continue
grep -Eq 'MonoBehaviour|ScriptableObject' "$file" || continue
found=1
UNITY_SCRIPT_COUNT=$((UNITY_SCRIPT_COUNT + 1))
rel="$(relative_path "$file")"
classes="$(extract_classes "$file" | join_names)"
base_classes="$(grep -Eoh 'class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*:[[:space:]]*[A-Za-z_][A-Za-z0-9_]*' "$file" | sed -E 's/.*:[[:space:]]*//' | join_names)"
lifecycle="$(grep -Eoh '\b(Awake|Start|Update|FixedUpdate|LateUpdate|OnEnable|OnDisable|OnDestroy|OnTriggerEnter|OnTriggerExit|OnCollisionEnter|OnCollisionExit|OnMouseDown|OnMouseUp)[[:space:]]*\(' "$file" | sed 's/[[:space:]]*(//' | join_names)"
fields="$(grep -Eoh '(\[SerializeField\][[:space:]]*)?(public|private|protected)?[[:space:]]*(readonly[[:space:]]+)?[A-Za-z_][A-Za-z0-9_<>,\[\]]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*(=|;)' "$file" | sed -E 's/[=;].*//; s/^[[:space:]]+|[[:space:]]+$//g' | head -20 | join_names)"
public_methods="$(grep -Eoh 'public[[:space:]]+[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" | sed -E 's/.*[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(/\1/' | join_names)"
unity_api="$(grep -Eoh 'UnityEvent|GetComponent|FindObjectOfType|FindFirstObjectByType|Instantiate|Destroy|SceneManager|Input\.' "$file" | join_names)"
role="$(unity_role_guess "$file")"
[[ -z "$classes" ]] && classes="not detected"
[[ -z "$base_classes" ]] && base_classes="not detected"
[[ -z "$lifecycle" ]] && lifecycle="none detected"
[[ -z "$fields" ]] && fields="none detected"
[[ -z "$public_methods" ]] && public_methods="none detected"
[[ -z "$unity_api" ]] && unity_api="none detected"
printf '## `%s`\n\n' "$rel"
printf -- '- Class name: %s\n' "$classes"
printf -- '- Base class: %s\n' "$base_classes"
printf -- '- Lifecycle methods detected: %s\n' "$lifecycle"
printf -- '- Serialized/public fields: %s\n' "$fields"
printf -- '- Public methods: %s\n' "$public_methods"
printf -- '- Unity API usage: %s\n' "$unity_api"
printf -- '- Possible script role: %s\n\n' "$role"
done
if [[ "$found" -eq 0 ]]; then
printf 'No Unity MonoBehaviour or ScriptableObject scripts detected.\n'
fi
} >"$UNITY_REPORT"
record_generated_file "$UNITY_REPORT"
}
extract_called_names() {
local file="$1"
grep -Eoh '\b[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" | sed -E 's/[[:space:]]*\($//' | grep -Ev '^(if|for|while|switch|catch|function|def|class|return|echo|print|new|sizeof)$' | join_names
}
write_call_map() {
{
printf '# Call Map\n\n'
printf 'Best-effort static scan. Dynamic/runtime calls may not be detected.\n\n'
printf '## Possible Entry Points\n\n'
local file rel language entry_found=0
for file in "${SOURCE_FILES[@]}"; do
if is_entry_point "$file"; then
rel="$(relative_path "$file")"
printf -- '- `%s`\n' "$rel"
ENTRY_POINT_COUNT=$((ENTRY_POINT_COUNT + 1))
entry_found=1
fi
done
[[ "$entry_found" -eq 0 ]] && printf -- '- No obvious entry points detected.\n'
printf '\n## Detected Imports/Includes\n\n'
for file in "${SOURCE_FILES[@]}"; do
rel="$(relative_path "$file")"
local imports
imports="$(extract_imports "$file" | join_names)"
[[ -n "$imports" ]] && printf -- '- `%s`: %s\n' "$rel" "$imports"
done
printf '\n## Function Definitions\n\n'
for file in "${SOURCE_FILES[@]}"; do
rel="$(relative_path "$file")"
language="$(language_for_file "$file")"
local functions
functions="$(extract_functions "$file" | sed -E 's/^[0-9]+://' | while IFS= read -r line; do function_name_from_line "$language" "$line"; done | join_names)"
[[ -n "$functions" ]] && printf -- '- `%s`: %s\n' "$rel" "$functions"
done
printf '\n## Function Call References\n\n'
for file in "${SOURCE_FILES[@]}"; do
rel="$(relative_path "$file")"
local calls
calls="$(extract_called_names "$file")"
[[ -n "$calls" ]] && printf -- '- `%s`: possible calls to %s\n' "$rel" "$calls"
done
printf '\n## Possible Call Relationships\n\n'
for file in "${SOURCE_FILES[@]}"; do
rel="$(relative_path "$file")"
local imports
imports="$(extract_imports "$file" | join_names)"
if [[ -n "$imports" ]]; then
printf -- '- `%s` may depend on imported or included code: %s\n' "$rel" "$imports"
fi
done
printf '\n## Limitations\n\n'
printf -- '- Best-effort static scan only.\n'
printf -- '- Runtime reflection, framework routing, dependency injection, dynamic imports, and generated code may not be detected.\n'
printf -- '- Unity lifecycle methods are entry-like runtime hooks, not direct command-line entry points.\n'
} >"$CALL_MAP_REPORT"
record_generated_file "$CALL_MAP_REPORT"
}
write_generated_files_report() {
if [[ -f "$GENERATED_REPORT" ]]; then
while IFS= read -r file; do
[[ -z "$file" ]] && continue
[[ "$file" == \#* ]] && continue
[[ "$file" == "- "* ]] || continue
record_generated_file "$ROOT_DIR/${file#- }"
done <"$GENERATED_REPORT"
fi
record_generated_file "$GENERATED_REPORT"
{
printf '# Generated Files\n\n'
local file
for file in "${GENERATED_FILES[@]}"; do
printf -- '- %s\n' "$file"
done
} >"$GENERATED_REPORT"
}
write_summary() {
record_generated_file "$SUMMARY_REPORT"
{
printf '# Code Documentation Summary\n\n'
printf '===== CODE DOCUMENTATION SUMMARY =====\n\n'
printf 'Reports written:\n'
printf -- '- reports/api-reference.md\n'
printf -- '- reports/file-purpose.md\n'
printf -- '- reports/unity-scripts.md\n'
printf -- '- reports/call-map.md\n'
printf -- '- reports/code-docs-summary.md\n\n'
printf 'Detected:\n'
printf -- '- Source files scanned: %s\n' "$SOURCE_COUNT"
printf -- '- Functions/methods detected: %s\n' "$FUNCTION_COUNT"
printf -- '- Classes detected: %s\n' "$CLASS_COUNT"
printf -- '- Unity scripts detected: %s\n' "$UNITY_SCRIPT_COUNT"
printf -- '- Possible entry points detected: %s\n' "$ENTRY_POINT_COUNT"
printf '\nDetected code types:\n'
if ((${#DETECTED_CODE_TYPES[@]} == 0)); then
printf -- '- none detected\n'
else
local type
for type in "${DETECTED_CODE_TYPES[@]}"; do
printf -- '- %s\n' "$type"
done
fi
} >"$SUMMARY_REPORT"
}
print_summary() {
printf '===== CODE DOCUMENTATION SUMMARY =====\n'
printf 'Reports written:\n'
printf -- '- reports/api-reference.md\n'
printf -- '- reports/file-purpose.md\n'
printf -- '- reports/unity-scripts.md\n'
printf -- '- reports/call-map.md\n'
printf -- '- reports/code-docs-summary.md\n'
printf 'Detected:\n'
printf -- '- Source files scanned: %s\n' "$SOURCE_COUNT"
printf -- '- Functions/methods detected: %s\n' "$FUNCTION_COUNT"
printf -- '- Classes detected: %s\n' "$CLASS_COUNT"
printf -- '- Unity scripts detected: %s\n' "$UNITY_SCRIPT_COUNT"
printf -- '- Possible entry points detected: %s\n' "$ENTRY_POINT_COUNT"
printf 'Detected code types:\n'
local type
if ((${#DETECTED_CODE_TYPES[@]} == 0)); then
printf -- '- none detected\n'
else
for type in "${DETECTED_CODE_TYPES[@]}"; do
printf -- '- %s\n' "$type"
done
fi
}
main() {
collect_source_files
SOURCE_COUNT="${#SOURCE_FILES[@]}"
local file language
for file in "${SOURCE_FILES[@]}"; do
language="$(language_for_file "$file")"
append_detected_type "$language"
if [[ "$language" == "C#" ]] && grep -Eq 'MonoBehaviour|ScriptableObject' "$file"; then
append_detected_type "Unity C# scripts"
fi
done
write_api_reference || exit 1
write_file_purpose || exit 1
write_unity_scripts || exit 1
write_call_map || exit 1
write_summary || exit 1
write_generated_files_report || exit 1
print_summary
}
main "$@"

589
tools/project-report.sh Executable file → Normal file
View file

@ -1,12 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
set -u -o pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPORT_DIR="$ROOT_DIR/reports"
FLOW_REPORT="$REPORT_DIR/project-flow.md"
FILE_REPORT="$REPORT_DIR/file-summary.md"
LANG_REPORT="$REPORT_DIR/languages.md"
SUMMARY_REPORT="$REPORT_DIR/summary.md"
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
STATUS_FILE="$REPORT_DIR/customer-code-status.txt"
ERROR_REPORT="$REPORT_DIR/errors.md"
EXCLUDES=(
".git"
@ -20,12 +24,60 @@ EXCLUDES=(
"bin"
)
mkdir -p "$REPORT_DIR"
GENERATED_FILES=()
declare -A LANGUAGE_COUNTS=(
["PHP"]=0
["JavaScript"]=0
["TypeScript"]=0
["Python"]=0
["C#"]=0
["C++"]=0
["Ruby"]=0
["Perl"]=0
["HTML"]=0
["CSS"]=0
["Shell"]=0
["JSON"]=0
["YAML"]=0
)
ENTRY_POINTS=()
STARTUP_SCRIPTS=()
TOP_LEVEL_DIRS=()
ENV_FILES=()
CONFIG_FILES=()
DOCKER_FILES=()
DB_CONFIGS=()
have_tool() {
command -v "$1" >/dev/null 2>&1
}
record_generated_file() {
local file="$1"
local rel="${file#"$ROOT_DIR"/}"
local existing
for existing in "${GENERATED_FILES[@]}"; do
if [[ "$existing" == "$rel" ]]; then
return
fi
done
GENERATED_FILES+=("$rel")
}
relative_path() {
local path="$1"
path="${path#"$ROOT_DIR"/}"
printf '%s' "$path"
}
ensure_report_dir() {
mkdir -p "$REPORT_DIR"
}
collect_files() {
local find_args=("$ROOT_DIR")
local exclude
@ -43,59 +95,62 @@ collect_files() {
find "${find_args[@]}"
}
relative_path() {
local path="$1"
path="${path#"$ROOT_DIR"/}"
printf '%s' "$path"
}
search_tree() {
local pattern="$1"
if have_tool rg; then
rg -qi "$pattern" "$ROOT_DIR" --glob '!node_modules/**' --glob '!vendor/**' --glob '!.git/**' --glob '!Library/**' --glob '!Temp/**' --glob '!Logs/**' --glob '!obj/**' --glob '!bin/**'
rg -qi -e "$pattern" "$ROOT_DIR" \
--glob '!.git/**' \
--glob '!.forgejo/**' \
--glob '!node_modules/**' \
--glob '!vendor/**' \
--glob '!Library/**' \
--glob '!Temp/**' \
--glob '!Logs/**' \
--glob '!obj/**' \
--glob '!bin/**'
else
grep -RqiE --exclude-dir=.git --exclude-dir=.forgejo --exclude-dir=node_modules --exclude-dir=vendor --exclude-dir=Library --exclude-dir=Temp --exclude-dir=Logs --exclude-dir=obj --exclude-dir=bin "$pattern" "$ROOT_DIR"
grep -RqiE \
--exclude-dir=.git \
--exclude-dir=.forgejo \
--exclude-dir=node_modules \
--exclude-dir=vendor \
--exclude-dir=Library \
--exclude-dir=Temp \
--exclude-dir=Logs \
--exclude-dir=obj \
--exclude-dir=bin \
"$pattern" "$ROOT_DIR"
fi
}
count_matches() {
local pattern="$1"
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -type f -name "$pattern" -print | wc -l | tr -d ' '
append_unique() {
local value="$1"
shift
local existing
for existing in "$@"; do
if [[ "$existing" == "$value" ]]; then
return 1
fi
done
return 0
}
declare -A LANGUAGE_COUNTS=(
["PHP"]=0
["JavaScript"]=0
["TypeScript"]=0
["Python"]=0
["C#"]=0
["C++"]=0
["Ruby"]=0
["Perl"]=0
["HTML"]=0
["CSS"]=0
["Shell"]=0
)
ENTRY_POINTS=()
STARTUP_SCRIPTS=()
TOP_LEVEL_DIRS=()
ENV_FILES=()
CONFIG_FILES=()
DOCKER_FILES=()
DB_CONFIGS=()
detect_entry_or_startup() {
local file="$1"
local base
local rel
base="$(basename "$file")"
rel="$(relative_path "$file")"
case "$base" in
index.php|index.html|index.htm|main.py|app.py|server.py|Program.cs|Main.cs|main.cpp|main.c)
ENTRY_POINTS+=("$(relative_path "$file")")
ENTRY_POINTS+=("$rel")
;;
start*.sh|launch*.sh|run*.sh|startup*.sh|*.bat|*.cmd)
STARTUP_SCRIPTS+=("$(relative_path "$file")")
STARTUP_SCRIPTS+=("$rel")
;;
esac
}
@ -103,6 +158,7 @@ detect_entry_or_startup() {
detect_config_files() {
local file="$1"
local rel
rel="$(relative_path "$file")"
case "$(basename "$file")" in
@ -115,7 +171,7 @@ detect_config_files() {
esac
case "$file" in
*.conf|*.config|*.ini|*.env|*.yaml|*.yml|*.json|*config*.php|*config*.py|*config*.rb)
*.conf|*.config|*.ini|*.env|*.yaml|*.yml|*.json|*config*.php|*config*.py|*config*.rb|*settings*.php)
CONFIG_FILES+=("$rel")
;;
esac
@ -141,6 +197,8 @@ classify_language() {
*.html|*.htm) LANGUAGE_COUNTS["HTML"]=$((LANGUAGE_COUNTS["HTML"] + 1)) ;;
*.css) LANGUAGE_COUNTS["CSS"]=$((LANGUAGE_COUNTS["CSS"] + 1)) ;;
*.sh|*.bash) LANGUAGE_COUNTS["Shell"]=$((LANGUAGE_COUNTS["Shell"] + 1)) ;;
*.json) LANGUAGE_COUNTS["JSON"]=$((LANGUAGE_COUNTS["JSON"] + 1)) ;;
*.yml|*.yaml) LANGUAGE_COUNTS["YAML"]=$((LANGUAGE_COUNTS["YAML"] + 1)) ;;
esac
}
@ -175,135 +233,354 @@ detect_databases() {
printf '%s\n' "${databases[@]}" | awk 'NF && !seen[$0]++'
}
while IFS= read -r -d '' file; do
classify_language "$file"
detect_entry_or_startup "$file"
detect_config_files "$file"
done < <(collect_files)
while IFS= read -r dir; do
TOP_LEVEL_DIRS+=("${dir#./}")
done < <(find "$ROOT_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.git' | sed "s#^$ROOT_DIR/##" | sort)
FRAMEWORKS="$(detect_frameworks | awk 'NF { if (count++) printf ", "; printf "%s", $0 }')"
DATABASES="$(detect_databases | awk 'NF { if (count++) printf ", "; printf "%s", $0 }')"
LIKELY_ENTRY="None detected"
if ((${#ENTRY_POINTS[@]} > 0)); then
LIKELY_ENTRY="${ENTRY_POINTS[0]}"
fi
{
printf '# Languages\n\n'
printf '| Language | Count |\n'
printf '|---|---:|\n'
for lang in "PHP" "JavaScript" "TypeScript" "Python" "C#" "C++" "Ruby" "Perl" "HTML" "CSS" "Shell"; do
printf '| %s | %s |\n' "$lang" "${LANGUAGE_COUNTS[$lang]}"
done
} >"$LANG_REPORT"
{
printf '# File Summary\n\n'
printf '## Top-Level Folders\n\n'
if ((${#TOP_LEVEL_DIRS[@]} == 0)); then
printf -- '- None detected\n'
read_customer_status() {
if [[ -f "$STATUS_FILE" ]]; then
head -n 1 "$STATUS_FILE"
elif [[ -f "$ERROR_REPORT" ]] && rg -q "CUSTOMER CODE ERRORS FOUND|NO CUSTOMER CODE ERRORS FOUND" "$ERROR_REPORT"; then
rg -m 1 "CUSTOMER CODE ERRORS FOUND|NO CUSTOMER CODE ERRORS FOUND" "$ERROR_REPORT"
else
printf -- '- %s\n' "${TOP_LEVEL_DIRS[@]}"
printf 'CUSTOMER CODE STATUS UNKNOWN\n'
fi
}
printf '\n## File Counts By Type\n\n'
printf '| Type | Count |\n'
printf '|---|---:|\n'
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
detect_repo_name() {
basename "$ROOT_DIR"
}
printf '\n## Largest Source Directories\n\n'
find "$ROOT_DIR" -mindepth 1 -maxdepth 2 -type d ! -path "$ROOT_DIR/.git*" ! -path "$ROOT_DIR/.forgejo*" -print0 \
| while IFS= read -r -d '' dir; do
detect_branch_name() {
if have_tool git && git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown'
else
printf 'unknown'
fi
}
detect_commit_hash() {
if have_tool git && git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git -C "$ROOT_DIR" rev-parse --short HEAD 2>/dev/null || printf 'unknown'
else
printf 'unknown'
fi
}
write_languages_report() {
{
printf '# Languages\n\n'
printf '| Language | Count |\n'
printf '|---|---:|\n'
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
printf '| JSON | %s |\n' "${LANGUAGE_COUNTS["JSON"]}"
printf '| YAML | %s |\n' "${LANGUAGE_COUNTS["YAML"]}"
} >"$LANG_REPORT"
record_generated_file "$LANG_REPORT"
}
write_file_summary_report() {
{
printf '# File Summary\n\n'
printf '## Top-Level Folders\n\n'
if ((${#TOP_LEVEL_DIRS[@]} == 0)); then
printf -- '- None detected\n'
else
local dir
for dir in "${TOP_LEVEL_DIRS[@]}"; do
printf -- '- %s\n' "$dir"
done
fi
printf '\n## File Counts By Type\n\n'
printf '| Type | Count |\n'
printf '|---|---:|\n'
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
printf '| JSON | %s |\n' "${LANGUAGE_COUNTS["JSON"]}"
printf '| YAML | %s |\n' "${LANGUAGE_COUNTS["YAML"]}"
printf '\n## Largest Source Directories\n\n'
find "$ROOT_DIR" -mindepth 1 -maxdepth 2 -type d \
! -path "$ROOT_DIR/.git*" \
! -path "$ROOT_DIR/.forgejo*" \
-print0 | while IFS= read -r -d '' dir; do
count="$(find "$dir" -type f | wc -l | tr -d ' ')"
printf '%s\t%s\n' "$count" "$(relative_path "$dir")"
done | sort -rn | head -n 10 | awk -F '\t' '{printf "- %s files: %s\n", $1, $2}'
} >"$FILE_REPORT"
} >"$FILE_REPORT"
{
printf '# Project Flow Report\n\n'
printf '## Likely Entry Point\n\n'
printf -- '- Likely entry point: %s\n' "$LIKELY_ENTRY"
record_generated_file "$FILE_REPORT"
}
printf '\n## Possible Startup Files\n\n'
if ((${#ENTRY_POINTS[@]} == 0 && ${#STARTUP_SCRIPTS[@]} == 0)); then
printf -- '- No obvious startup files detected.\n'
else
if ((${#ENTRY_POINTS[@]} > 0)); then
printf -- '- Possible startup file: %s\n' "${ENTRY_POINTS[@]}"
fi
if ((${#STARTUP_SCRIPTS[@]} > 0)); then
printf -- '- Possible launch script: %s\n' "${STARTUP_SCRIPTS[@]}"
fi
fi
write_project_flow_report() {
local frameworks="$1"
local databases="$2"
local likely_entry="No obvious entry point detected"
printf '\n## Detected Frameworks\n\n'
if [[ -n "$FRAMEWORKS" ]]; then
IFS=',' read -r -a framework_items <<<"$FRAMEWORKS"
for item in "${framework_items[@]}"; do
printf -- '- %s\n' "$(echo "$item" | sed 's/^ *//; s/ *$//')"
done
else
printf -- '- No supported framework markers were detected.\n'
fi
printf '\n## Detected Databases\n\n'
if [[ -n "$DATABASES" ]]; then
IFS=',' read -r -a database_items <<<"$DATABASES"
for item in "${database_items[@]}"; do
printf -- '- %s\n' "$(echo "$item" | sed 's/^ *//; s/ *$//')"
done
else
printf -- '- No database markers were detected.\n'
fi
printf '\n## Configuration Files\n\n'
if ((${#ENV_FILES[@]} > 0)); then
printf -- '- Possible .env files: %s\n' "${ENV_FILES[@]}"
else
printf -- '- No .env files detected.\n'
fi
if ((${#DOCKER_FILES[@]} > 0)); then
printf -- '- Docker-related files: %s\n' "${DOCKER_FILES[@]}"
else
printf -- '- No Docker-related files detected.\n'
fi
if ((${#DB_CONFIGS[@]} > 0)); then
printf -- '- Possible database config files: %s\n' "${DB_CONFIGS[@]}"
else
printf -- '- No obvious database config files detected.\n'
fi
printf '\n## Important Files To Review\n\n'
if ((${#ENTRY_POINTS[@]} > 0)); then
printf -- '- %s\n' "${ENTRY_POINTS[@]}"
fi
if ((${#CONFIG_FILES[@]} > 0)); then
printf -- '- %s\n' "${CONFIG_FILES[@]:0:10}"
else
printf -- '- No common config files detected.\n'
likely_entry="${ENTRY_POINTS[0]}"
fi
printf '\n## Probable Application Flow\n\n'
printf 'This is a best-effort analysis only.\n\n'
printf -- '- Probable application flow starts near `%s`.\n' "$LIKELY_ENTRY"
printf -- '- Likely supporting behavior is configured through nearby config files, environment files, or workflow files.\n'
printf -- '- If framework markers are present, startup and routing probably follow that framework'"'"'s conventions.\n'
printf -- '- If no framework markers are present, the repository likely uses a simpler file-based startup flow.\n'
} >"$FLOW_REPORT"
{
printf '# Project Flow Report\n\n'
printf '## Likely Entry Point\n\n'
printf -- '- Likely entry point: %s\n' "$likely_entry"
exit 0
printf '\n## Possible Startup Files\n\n'
if ((${#ENTRY_POINTS[@]} == 0 && ${#STARTUP_SCRIPTS[@]} == 0)); then
printf -- '- No obvious startup files detected.\n'
else
local entry
for entry in "${ENTRY_POINTS[@]}"; do
printf -- '- Possible startup file: %s\n' "$entry"
done
local script
for script in "${STARTUP_SCRIPTS[@]}"; do
printf -- '- Possible launch script: %s\n' "$script"
done
fi
printf '\n## Detected Frameworks\n\n'
if [[ -n "$frameworks" ]]; then
printf '%s\n' "$frameworks" | while IFS= read -r item; do
[[ -n "$item" ]] && printf -- '- Detected framework: %s\n' "$item"
done
else
printf -- '- No supported framework markers were detected.\n'
fi
printf '\n## Detected Databases\n\n'
if [[ -n "$databases" ]]; then
printf '%s\n' "$databases" | while IFS= read -r item; do
[[ -n "$item" ]] && printf -- '- Possible database usage: %s\n' "$item"
done
else
printf -- '- No database markers were detected.\n'
fi
printf '\n## Configuration Files\n\n'
if ((${#ENV_FILES[@]} > 0)); then
printf -- '- Possible .env files: %s\n' "${ENV_FILES[*]}"
else
printf -- '- No .env files detected.\n'
fi
if ((${#DOCKER_FILES[@]} > 0)); then
printf -- '- Docker-related files: %s\n' "${DOCKER_FILES[*]}"
else
printf -- '- No Docker-related files detected.\n'
fi
if ((${#DB_CONFIGS[@]} > 0)); then
printf -- '- Possible database config files: %s\n' "${DB_CONFIGS[*]}"
else
printf -- '- No obvious database config files detected.\n'
fi
printf '\n## Important Files To Review\n\n'
if ((${#ENTRY_POINTS[@]} > 0)); then
local entry
for entry in "${ENTRY_POINTS[@]}"; do
printf -- '- %s\n' "$entry"
done
fi
if ((${#CONFIG_FILES[@]} > 0)); then
local max_items=10
local i
for ((i = 0; i < ${#CONFIG_FILES[@]} && i < max_items; i++)); do
printf -- '- %s\n' "${CONFIG_FILES[$i]}"
done
else
printf -- '- No common config files detected.\n'
fi
printf '\n## Probable Application Flow\n\n'
printf 'This is a best-effort analysis only.\n\n'
printf -- '- Probable application flow starts near `%s`.\n' "$likely_entry"
printf -- '- Likely supporting behavior is configured through nearby config files, environment files, or workflow files.\n'
printf -- '- If framework markers are present, startup and routing probably follow that framework'"'"'s conventions.\n'
printf -- '- If no framework markers are present, the repository likely uses a simpler file-based startup flow.\n'
} >"$FLOW_REPORT"
record_generated_file "$FLOW_REPORT"
}
write_summary_report() {
local customer_status="$1"
local frameworks="$2"
local databases="$3"
local repo_name branch_name commit_hash likely_entry
local generated_items=()
repo_name="$(detect_repo_name)"
branch_name="$(detect_branch_name)"
commit_hash="$(detect_commit_hash)"
likely_entry="No obvious entry point detected"
if ((${#ENTRY_POINTS[@]} > 0)); then
likely_entry="${ENTRY_POINTS[0]}"
fi
generated_items=("${GENERATED_FILES[@]}")
if append_unique "reports/summary.md" "${generated_items[@]}"; then
generated_items+=("reports/summary.md")
fi
{
printf '# Automation Summary\n\n'
printf -- '- Generated: %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')"
printf -- '- Repository: %s\n' "$repo_name"
printf -- '- Branch: %s\n' "$branch_name"
printf -- '- Commit: %s\n' "$commit_hash"
printf -- '- Customer code status: %s\n\n' "$customer_status"
printf '## Files Created Or Updated By This Run\n\n'
local file
for file in "${generated_items[@]}"; do
printf -- '- %s\n' "$file"
done
printf '\n## Short Project Overview\n\n'
printf 'This repository is being analyzed as a customer debugging project. The automation generates validation and structure reports without treating customer code errors as workflow failures.\n'
printf '\n## Language Counts\n\n'
printf -- '- PHP: %s\n' "${LANGUAGE_COUNTS["PHP"]}"
printf -- '- JavaScript: %s\n' "${LANGUAGE_COUNTS["JavaScript"]}"
printf -- '- TypeScript: %s\n' "${LANGUAGE_COUNTS["TypeScript"]}"
printf -- '- Python: %s\n' "${LANGUAGE_COUNTS["Python"]}"
printf -- '- C#: %s\n' "${LANGUAGE_COUNTS["C#"]}"
printf -- '- C++: %s\n' "${LANGUAGE_COUNTS["C++"]}"
printf -- '- Ruby: %s\n' "${LANGUAGE_COUNTS["Ruby"]}"
printf -- '- Perl: %s\n' "${LANGUAGE_COUNTS["Perl"]}"
printf -- '- HTML: %s\n' "${LANGUAGE_COUNTS["HTML"]}"
printf -- '- CSS: %s\n' "${LANGUAGE_COUNTS["CSS"]}"
printf -- '- Shell: %s\n' "${LANGUAGE_COUNTS["Shell"]}"
printf -- '- JSON: %s\n' "${LANGUAGE_COUNTS["JSON"]}"
printf -- '- YAML: %s\n' "${LANGUAGE_COUNTS["YAML"]}"
printf '\n## Likely Entry Points\n\n'
if ((${#ENTRY_POINTS[@]} > 0)); then
local entry
for entry in "${ENTRY_POINTS[@]}"; do
printf -- '- %s\n' "$entry"
done
else
printf -- '- %s\n' "$likely_entry"
fi
printf '\n## Detected Frameworks\n\n'
if [[ -n "$frameworks" ]]; then
printf '%s\n' "$frameworks" | while IFS= read -r item; do
[[ -n "$item" ]] && printf -- '- %s\n' "$item"
done
else
printf -- '- None detected\n'
fi
printf '\n## Detected Databases\n\n'
if [[ -n "$databases" ]]; then
printf '%s\n' "$databases" | while IFS= read -r item; do
[[ -n "$item" ]] && printf -- '- %s\n' "$item"
done
else
printf -- '- None detected\n'
fi
printf '\n## Report Files Generated\n\n'
local generated
for generated in "${generated_items[@]}"; do
printf -- '- %s\n' "$generated"
done
} >"$SUMMARY_REPORT"
record_generated_file "$SUMMARY_REPORT"
}
write_generated_files_report() {
if [[ -f "$GENERATED_REPORT" ]]; then
while IFS= read -r file; do
[[ -z "$file" ]] && continue
[[ "$file" == \#* ]] && continue
[[ "$file" == "- "* ]] || continue
file="${file#- }"
record_generated_file "$ROOT_DIR/$file"
done <"$GENERATED_REPORT"
fi
{
printf '# Generated Files\n\n'
if ((${#GENERATED_FILES[@]} == 0)); then
printf 'No report files were generated.\n'
else
local file
for file in "${GENERATED_FILES[@]}"; do
printf -- '- %s\n' "$file"
done
fi
} >"$GENERATED_REPORT"
record_generated_file "$GENERATED_REPORT"
}
main() {
ensure_report_dir || exit 1
while IFS= read -r -d '' file; do
classify_language "$file"
detect_entry_or_startup "$file"
detect_config_files "$file"
done < <(collect_files)
while IFS= read -r dir; do
TOP_LEVEL_DIRS+=("$dir")
done < <(find "$ROOT_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.git' | sed "s#^$ROOT_DIR/##" | sort)
local frameworks databases customer_status
frameworks="$(detect_frameworks)"
databases="$(detect_databases)"
customer_status="$(read_customer_status)"
write_languages_report || exit 1
write_file_summary_report || exit 1
write_project_flow_report "$frameworks" "$databases" || exit 1
write_generated_files_report || exit 1
write_summary_report "$customer_status" "$frameworks" "$databases" || exit 1
write_generated_files_report || exit 1
printf '===== PROJECT REPORT COMPLETE =====\n'
printf 'Report generation completed\n'
printf 'Files created or updated:\n'
local file
for file in "${GENERATED_FILES[@]}"; do
printf -- '- %s\n' "$file"
done
printf 'Customer code status:\n'
printf '%s\n' "$customer_status"
if [[ -f "$ERROR_REPORT" ]]; then
printf '\nValidation summary excerpt:\n'
sed -n '1,8p' "$ERROR_REPORT"
fi
if [[ -f "$SUMMARY_REPORT" ]]; then
printf '\nAutomation summary excerpt:\n'
sed -n '1,20p' "$SUMMARY_REPORT"
fi
}
main "$@"

178
tools/validate-code.sh Executable file → Normal file
View file

@ -1,16 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
set -u -o pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPORT_DIR="$ROOT_DIR/reports"
ERROR_REPORT="$REPORT_DIR/errors.md"
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
STATUS_FILE="$REPORT_DIR/customer-code-status.txt"
TMP_DIR=""
FILES_CHECKED=0
WARNINGS=0
ERRORS=0
DETAILS=()
GENERATED_FILES=()
EXCLUDES=(
".git"
@ -24,7 +28,27 @@ EXCLUDES=(
"bin"
)
mkdir -p "$REPORT_DIR"
cleanup() {
if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
record_generated_file() {
local file="$1"
local rel="${file#"$ROOT_DIR"/}"
local existing
for existing in "${GENERATED_FILES[@]}"; do
if [[ "$existing" == "$rel" ]]; then
return
fi
done
GENERATED_FILES+=("$rel")
}
escape_md() {
printf '%s' "$1" | sed 's/|/\\|/g'
@ -34,6 +58,13 @@ have_tool() {
command -v "$1" >/dev/null 2>&1
}
ensure_report_dir() {
mkdir -p "$REPORT_DIR" || {
printf 'Failed to create report directory: %s\n' "$REPORT_DIR" >&2
return 1
}
}
add_warning() {
local message="$1"
WARNINGS=$((WARNINGS + 1))
@ -62,7 +93,10 @@ join_by() {
shift || true
local first=1
local item
for item in "$@"; do
[[ -z "$item" ]] && continue
if [[ $first -eq 1 ]]; then
printf '%s' "$item"
first=0
@ -94,14 +128,22 @@ run_check() {
local file="$2"
shift 2
local stdout_file="$TMP_DIR/stdout.log"
local stderr_file="$TMP_DIR/stderr.log"
local stdout=""
local stderr=""
local combined=""
FILES_CHECKED=$((FILES_CHECKED + 1))
if "$@" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
: >"$stdout_file"
: >"$stderr_file"
if "$@" >"$stdout_file" 2>"$stderr_file"; then
add_ok "$label passed: $(relative_path "$file")"
else
local stdout stderr combined
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stdout="$(tr '\n' ' ' <"$stdout_file" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stderr="$(tr '\n' ' ' <"$stderr_file" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
add_error "$label failed: $(relative_path "$file")${combined:+ | $combined}"
fi
@ -111,46 +153,48 @@ run_repo_build_checks() {
local sln_files=()
local csproj_files=()
local cmake_file="$ROOT_DIR/CMakeLists.txt"
local tmp_dir
local tmp_cmake_dir=""
while IFS= read -r -d '' file; do
sln_files+=("$file")
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.sln' -type f -print0)
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -path "$ROOT_DIR/.forgejo" -prune -o -name '*.sln' -type f -print0)
while IFS= read -r -d '' file; do
csproj_files+=("$file")
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print0)
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -path "$ROOT_DIR/.forgejo" -prune -o -name '*.csproj' -type f -print0)
if ((${#sln_files[@]} > 0 || ${#csproj_files[@]} > 0)); then
if have_tool dotnet; then
if ((${#sln_files[@]} > 0)); then
local file
for file in "${sln_files[@]}"; do
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
done
else
local file
for file in "${csproj_files[@]}"; do
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
done
fi
else
add_warning "dotnet not installed; skipping C# validation."
add_warning "dotnet not installed; skipping C# project validation."
fi
fi
if [[ -f "$cmake_file" ]]; then
if have_tool cmake; then
FILES_CHECKED=$((FILES_CHECKED + 1))
tmp_dir="$(mktemp -d)"
if cmake -S "$ROOT_DIR" -B "$tmp_dir" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
tmp_cmake_dir="$(mktemp -d)"
if cmake -S "$ROOT_DIR" -B "$tmp_cmake_dir" >"$TMP_DIR/stdout.log" 2>"$TMP_DIR/stderr.log"; then
add_ok "cmake configure passed: CMakeLists.txt"
else
local stdout stderr combined
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stdout="$(tr '\n' ' ' <"$TMP_DIR/stdout.log" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
stderr="$(tr '\n' ' ' <"$TMP_DIR/stderr.log" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
add_error "cmake configure failed: CMakeLists.txt${combined:+ | $combined}"
fi
rm -rf "$tmp_dir"
rm -rf "$tmp_cmake_dir"
else
add_warning "cmake not installed; skipping C/C++ validation."
fi
@ -236,29 +280,93 @@ validate_file() {
esac
}
while IFS= read -r -d '' file; do
validate_file "$file"
done < <(collect_files)
write_generated_files_report() {
{
printf '# Generated Files\n\n'
if ((${#GENERATED_FILES[@]} == 0)); then
printf 'No report files were generated.\n'
else
local file
for file in "${GENERATED_FILES[@]}"; do
printf -- '- %s\n' "$file"
done
fi
} >"$GENERATED_REPORT" || return 1
run_repo_build_checks
record_generated_file "$GENERATED_REPORT"
}
{
printf '# Validation Summary\n\n'
printf -- '- Files Checked: %s\n' "$FILES_CHECKED"
printf -- '- Warnings: %s\n' "$WARNINGS"
printf -- '- Errors: %s\n\n' "$ERRORS"
printf '## Detailed Results\n\n'
if ((${#DETAILS[@]} == 0)); then
printf 'No validation steps were run.\n'
else
printf '%s\n' "${DETAILS[@]}"
write_status_file() {
local status_line="NO CUSTOMER CODE ERRORS FOUND"
if ((ERRORS > 0)); then
status_line="CUSTOMER CODE ERRORS FOUND"
fi
} >"$ERROR_REPORT"
rm -f /tmp/runlevel-validate.out /tmp/runlevel-validate.err
printf '%s\n' "$status_line" >"$STATUS_FILE" || return 1
record_generated_file "$STATUS_FILE"
}
if ((ERRORS > 0)); then
exit 1
fi
write_error_report() {
local status_line="NO CUSTOMER CODE ERRORS FOUND"
if ((ERRORS > 0)); then
status_line="CUSTOMER CODE ERRORS FOUND"
fi
exit 0
{
printf '# Validation Summary\n\n'
printf '%s\n\n' "$status_line"
printf -- '- Files Checked: %s\n' "$FILES_CHECKED"
printf -- '- Warnings: %s\n' "$WARNINGS"
printf -- '- Errors: %s\n\n' "$ERRORS"
printf '## Detailed Results\n\n'
if ((${#DETAILS[@]} == 0)); then
printf 'No validation steps were run.\n'
else
printf '%s\n' "${DETAILS[@]}"
fi
} >"$ERROR_REPORT" || return 1
record_generated_file "$ERROR_REPORT"
}
print_terminal_summary() {
local status_line="NO CUSTOMER CODE ERRORS FOUND"
if ((ERRORS > 0)); then
status_line="CUSTOMER CODE ERRORS FOUND"
fi
printf '===== VALIDATION SUMMARY =====\n'
printf '%s\n' "$status_line"
printf 'Files checked: %s\n' "$FILES_CHECKED"
printf 'Warnings: %s\n' "$WARNINGS"
printf 'Errors: %s\n' "$ERRORS"
printf 'Reports written:\n'
local file
for file in "${GENERATED_FILES[@]}"; do
printf -- '- %s\n' "$file"
done
}
main() {
ensure_report_dir || return 1
TMP_DIR="$(mktemp -d)" || {
printf 'Failed to create temporary directory.\n' >&2
return 1
}
while IFS= read -r -d '' file; do
validate_file "$file"
done < <(collect_files)
run_repo_build_checks
write_status_file || return 1
write_error_report || return 1
write_generated_files_report || return 1
print_terminal_summary
return 0
}
main "$@"