generated from DevPlus1/Unity-Customer-Template
Initial commit
This commit is contained in:
commit
ed87277ef8
64 changed files with 4639 additions and 0 deletions
47
.forgejo/workflows/code-docs.yml
Normal file
47
.forgejo/workflows/code-docs.yml
Normal 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
|
||||
64
.forgejo/workflows/project-report.yml
Normal file
64
.forgejo/workflows/project-report.yml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
name: Project Report
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
project-report:
|
||||
runs-on: linux-dev
|
||||
|
||||
steps:
|
||||
- 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
|
||||
52
.forgejo/workflows/validate.yml
Normal file
52
.forgejo/workflows/validate.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
name: Validate Code
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: linux-dev
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
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
|
||||
34
Assets/RL_DevPlus1/Docs/AI_CONTEXT.md
Normal file
34
Assets/RL_DevPlus1/Docs/AI_CONTEXT.md
Normal 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.
|
||||
30
Assets/RL_DevPlus1/Docs/CODEX_INSTRUCTIONS.md
Normal file
30
Assets/RL_DevPlus1/Docs/CODEX_INSTRUCTIONS.md
Normal 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.
|
||||
26
Assets/RL_DevPlus1/Docs/README.md
Normal file
26
Assets/RL_DevPlus1/Docs/README.md
Normal 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.
|
||||
28
Assets/RL_DevPlus1/Docs/REPORTS.md
Normal file
28
Assets/RL_DevPlus1/Docs/REPORTS.md
Normal 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.
|
||||
47
Assets/RL_DevPlus1/Docs/UNITY_ANALYZER.md
Normal file
47
Assets/RL_DevPlus1/Docs/UNITY_ANALYZER.md
Normal 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/`.
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLGitReportImporter.cs
Normal file
114
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLGitReportImporter.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
567
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLReportExporter.cs
Normal file
567
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLReportExporter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
193
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLSceneAnalyzer.cs
Normal file
193
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLSceneAnalyzer.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLScriptAnalyzer.cs
Normal file
147
Assets/RL_DevPlus1/Editor/ProjectAnalyzer/RLScriptAnalyzer.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
Assets/RL_DevPlus1/Reports/README.md
Normal file
5
Assets/RL_DevPlus1/Reports/README.md
Normal 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`.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.json
Normal file
3
Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"status": "not generated yet"
|
||||
}
|
||||
3
Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md
Normal 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.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Event_Map.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Event_Map.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Event Map
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Execution_Order.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Execution_Order.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Execution Order
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_GameObject_Map.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_GameObject_Map.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity GameObject Map
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Git Report Summary
|
||||
|
||||
Not generated yet. Use `Runlevel Systems/DevPlus1/Refresh Git Reports` or generate all reports.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Missing References
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Prefab_Map.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Prefab_Map.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Prefab Map
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Project_Data.json
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Project_Data.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"status": "not generated yet"
|
||||
}
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Project_Summary.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Project_Summary.md
Normal 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`.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Report_Index.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Report_Index.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Report Index
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Scene_Map.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Scene_Map.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Scene Map
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
3
Assets/RL_DevPlus1/Reports/RL_Unity_Script_Map.md
Normal file
3
Assets/RL_DevPlus1/Reports/RL_Unity_Script_Map.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Runlevel Unity Script Map
|
||||
|
||||
Not generated yet. Run the Unity analyzer to populate this report.
|
||||
4
Assets/RL_DevPlus1/Runtime/README.txt
Normal file
4
Assets/RL_DevPlus1/Runtime/README.txt
Normal 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/
|
||||
306
README.md
Normal file
306
README.md
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
# Customer Unity Template
|
||||
|
||||
## Overview
|
||||
|
||||
The Customer Unity Template is the standard onboarding repository used by Runlevel Systems DevPlus1 when working with customer Unity projects.
|
||||
|
||||
This repository provides:
|
||||
|
||||
- Automated Forgejo validation workflows
|
||||
- Source code documentation generation
|
||||
- API reference generation
|
||||
- Static call map generation
|
||||
- Project structure reporting
|
||||
- Unity Editor project analysis tools
|
||||
- AI-ready project context generation
|
||||
- Standardized documentation for developers and AI assistants
|
||||
|
||||
The goal is to quickly understand a customer project without manually reviewing every file and to provide a consistent workflow across all Unity customer engagements.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Repository Structure
|
||||
|
||||
customer-unity-template/
|
||||
│
|
||||
├── .forgejo/
|
||||
│ └── workflows/
|
||||
│
|
||||
├── tools/
|
||||
│
|
||||
├── docs/
|
||||
│
|
||||
├── RL_DOCS/
|
||||
│
|
||||
├── Assets/
|
||||
│ └── RL_DevPlus1/
|
||||
│ ├── Editor/
|
||||
│ ├── Runtime/
|
||||
│ ├── Reports/
|
||||
│ └── Docs/
|
||||
│
|
||||
├── README.md
|
||||
└── .gitignore
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Workflow Overview
|
||||
|
||||
## Phase 1 - Customer Repository Import
|
||||
|
||||
Customer source code is imported into a repository created from this template.
|
||||
|
||||
Examples:
|
||||
|
||||
- Unity projects
|
||||
- Unity game prototypes
|
||||
- Mobile game projects
|
||||
- Business simulation projects
|
||||
- Training simulator projects
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## Phase 2 - Forgejo Validation
|
||||
|
||||
When code is pushed:
|
||||
|
||||
Developer Push
|
||||
↓
|
||||
Forgejo Actions
|
||||
↓
|
||||
Validation Scripts
|
||||
↓
|
||||
Documentation Generation
|
||||
↓
|
||||
Reports Created
|
||||
|
||||
Validation performs:
|
||||
|
||||
- PHP syntax checking
|
||||
- C# compilation validation when possible
|
||||
- JavaScript validation
|
||||
- Python validation
|
||||
- Shell script validation
|
||||
- YAML validation
|
||||
- Additional language support as implemented
|
||||
|
||||
Generated reports are stored in:
|
||||
|
||||
reports/
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## Phase 3 - Documentation Generation
|
||||
|
||||
The repository automatically generates:
|
||||
|
||||
### API Reference
|
||||
|
||||
reports/api-reference.md
|
||||
|
||||
Contains:
|
||||
|
||||
- Classes
|
||||
- Functions
|
||||
- Methods
|
||||
- Parameters
|
||||
- Return types
|
||||
- File locations
|
||||
|
||||
### File Purpose Report
|
||||
|
||||
reports/file-purpose.md
|
||||
|
||||
Contains:
|
||||
|
||||
- File descriptions
|
||||
- Likely responsibilities
|
||||
- Dependencies
|
||||
- High-level purpose
|
||||
|
||||
### Call Map
|
||||
|
||||
reports/call-map.md
|
||||
|
||||
Contains:
|
||||
|
||||
- Possible execution flow
|
||||
- Function relationships
|
||||
- Entry points
|
||||
- Dependencies
|
||||
|
||||
### Validation Results
|
||||
|
||||
reports/errors.md
|
||||
|
||||
Contains:
|
||||
|
||||
- Validation failures
|
||||
- Syntax errors
|
||||
- Build issues
|
||||
- Warnings
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Unity Analyzer
|
||||
|
||||
The Unity-specific analysis tools are located under:
|
||||
|
||||
Assets/RL_DevPlus1/
|
||||
|
||||
These tools are automatically available within the Unity Editor.
|
||||
|
||||
Menu:
|
||||
|
||||
Runlevel Systems
|
||||
└── DevPlus1
|
||||
├── Analyze Unity Project
|
||||
├── Refresh Git Reports
|
||||
└── Open Report Folder
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Unity Analysis Reports
|
||||
|
||||
Generated inside:
|
||||
|
||||
Assets/RL_DevPlus1/Reports/
|
||||
|
||||
Reports include:
|
||||
|
||||
RL_Unity_Project_Summary.md
|
||||
RL_Unity_Scene_Map.md
|
||||
RL_Unity_GameObject_Map.md
|
||||
RL_Unity_Script_Map.md
|
||||
RL_Unity_Event_Map.md
|
||||
RL_Unity_Prefab_Map.md
|
||||
RL_Unity_Execution_Order.md
|
||||
RL_Unity_Missing_References.md
|
||||
RL_AI_Project_Context.md
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# AI Integration
|
||||
|
||||
The repository is designed so AI assistants can understand a project without reading every source file.
|
||||
|
||||
Recommended AI reading order:
|
||||
|
||||
1. Assets/RL_DevPlus1/Reports/RL_AI_Project_Context.md
|
||||
2. Assets/RL_DevPlus1/Reports/RL_Unity_Project_Summary.md
|
||||
3. reports/api-reference.md
|
||||
4. reports/call-map.md
|
||||
5. Assets/RL_DevPlus1/Reports/RL_Unity_Scene_Map.md
|
||||
6. Assets/RL_DevPlus1/Reports/RL_Unity_Script_Map.md
|
||||
7. Assets/RL_DevPlus1/Reports/RL_Unity_Event_Map.md
|
||||
|
||||
This dramatically reduces analysis time for:
|
||||
|
||||
- Codex
|
||||
- ChatGPT
|
||||
- Claude
|
||||
- Future DevPlus1 automation systems
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Developer Workflow
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Clone repository:
|
||||
|
||||
git clone <repository>
|
||||
|
||||
Open project in Unity.
|
||||
|
||||
Allow Unity to compile.
|
||||
|
||||
Run:
|
||||
|
||||
Runlevel Systems
|
||||
→ DevPlus1
|
||||
→ Analyze Unity Project
|
||||
|
||||
Generate reports.
|
||||
|
||||
Review:
|
||||
|
||||
Assets/RL_DevPlus1/Reports/
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## Customer Review Process
|
||||
|
||||
1. Import customer project.
|
||||
2. Allow Forgejo validation to run.
|
||||
3. Review generated reports.
|
||||
4. Open Unity project.
|
||||
5. Run Unity analysis.
|
||||
6. Review AI context report.
|
||||
7. Begin development.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Design Principles
|
||||
|
||||
This template follows several rules:
|
||||
|
||||
- Read-only project analysis.
|
||||
- No automatic modification of customer assets.
|
||||
- No automatic scene saving.
|
||||
- No modification of customer code unless requested.
|
||||
- All Runlevel tools remain isolated under:
|
||||
|
||||
Assets/RL_DevPlus1/
|
||||
|
||||
- Generated reports remain isolated from customer assets.
|
||||
- AI-readable reports are always generated.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# RL_DOCS Folder
|
||||
|
||||
The RL_DOCS folder contains documentation intended for developers and AI assistants.
|
||||
|
||||
Recommended files:
|
||||
|
||||
RL_DOCS/
|
||||
├── README.md
|
||||
├── CODEX_INSTRUCTIONS.md
|
||||
├── WORKFLOWS.md
|
||||
├── REPORTS.md
|
||||
├── TEMPLATE_USAGE.md
|
||||
└── CUSTOMER_ONBOARDING.md
|
||||
|
||||
Purpose:
|
||||
|
||||
- Explain repository structure.
|
||||
- Explain workflows.
|
||||
- Explain generated reports.
|
||||
- Explain customer onboarding.
|
||||
- Provide instructions to Codex and future AI assistants.
|
||||
|
||||
Whenever possible, AI assistants should read RL_DOCS/CODEX_INSTRUCTIONS.md before analyzing customer code.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Future Templates
|
||||
|
||||
This repository serves as the foundation for:
|
||||
|
||||
- customer-unity-template
|
||||
- customer-unreal-template
|
||||
- customer-dotnet-template
|
||||
- customer-web-template
|
||||
- customer-python-template
|
||||
- customer-generic-template
|
||||
|
||||
Each future template will share the same DevPlus1 onboarding and documentation philosophy.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
# Runlevel Systems
|
||||
|
||||
Design. Debug. Deploy.
|
||||
|
||||
The purpose of this template is to allow Runlevel Systems developers to quickly understand, validate, document, and extend customer projects while maintaining a consistent workflow across all engagements.
|
||||
43
docs/ARCHITECTURE.md
Normal file
43
docs/ARCHITECTURE.md
Normal 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.
|
||||
113
docs/AUTOMATION.md
Normal file
113
docs/AUTOMATION.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Automation
|
||||
|
||||
## Intentional Behavior
|
||||
|
||||
This repository is for customer debugging projects.
|
||||
|
||||
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 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 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`
|
||||
|
||||
- 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`
|
||||
|
||||
- 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`
|
||||
|
||||
## Validation Sample Files
|
||||
|
||||
The repository includes intentional validator fixtures under `tests/validation-samples/`.
|
||||
|
||||
These files exist to prove:
|
||||
|
||||
- valid samples are accepted
|
||||
- broken samples are reported
|
||||
- broken customer code does not force the workflow red
|
||||
|
||||
## Extending The System
|
||||
|
||||
To add more validators later:
|
||||
|
||||
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`
|
||||
|
||||
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`
|
||||
52
docs/CODEX_INSTRUCTIONS.md
Normal file
52
docs/CODEX_INSTRUCTIONS.md
Normal 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
43
docs/README.md
Normal 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
73
docs/REPORTS.md
Normal 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
36
docs/REPOSITORY_MAP.md
Normal 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
78
docs/VALIDATORS.md
Normal 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
103
docs/WORKFLOWS.md
Normal 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.
|
||||
1
reports/.gitkeep
Normal file
1
reports/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
77
reports/api-reference.md
Normal file
77
reports/api-reference.md
Normal 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
35
reports/call-map.md
Normal 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.
|
||||
24
reports/code-docs-summary.md
Normal file
24
reports/code-docs-summary.md
Normal 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
|
||||
1
reports/customer-code-status.txt
Normal file
1
reports/customer-code-status.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
CUSTOMER CODE ERRORS FOUND
|
||||
20
reports/errors.md
Normal file
20
reports/errors.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Validation Summary
|
||||
|
||||
CUSTOMER CODE ERRORS FOUND
|
||||
|
||||
- Files Checked: 9
|
||||
- Warnings: 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: 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
18
reports/file-purpose.md
Normal 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
35
reports/file-summary.md
Normal 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
|
||||
14
reports/generated-files.md
Normal file
14
reports/generated-files.md
Normal 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
17
reports/languages.md
Normal 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
41
reports/project-flow.md
Normal 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
63
reports/summary.md
Normal 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
5
reports/unity-scripts.md
Normal 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.
|
||||
7
tests/validation-samples/csharp/broken.cs
Normal file
7
tests/validation-samples/csharp/broken.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
public class ValidationSampleBroken
|
||||
{
|
||||
public int Answer()
|
||||
{
|
||||
return 42
|
||||
}
|
||||
}
|
||||
7
tests/validation-samples/csharp/valid.cs
Normal file
7
tests/validation-samples/csharp/valid.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
public class ValidationSampleValid
|
||||
{
|
||||
public int Answer()
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
1
tests/validation-samples/javascript/broken.js
Normal file
1
tests/validation-samples/javascript/broken.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
console.log("broken javascript sample"
|
||||
1
tests/validation-samples/javascript/valid.js
Normal file
1
tests/validation-samples/javascript/valid.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
console.log("valid javascript sample");
|
||||
2
tests/validation-samples/php/broken.php
Normal file
2
tests/validation-samples/php/broken.php
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
echo "broken php sample"
|
||||
2
tests/validation-samples/php/valid.php
Normal file
2
tests/validation-samples/php/valid.php
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
echo "valid php sample";
|
||||
1
tests/validation-samples/python/broken.py
Normal file
1
tests/validation-samples/python/broken.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
print("broken python sample"
|
||||
1
tests/validation-samples/python/valid.py
Normal file
1
tests/validation-samples/python/valid.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
print("valid python sample")
|
||||
675
tools/generate-code-docs.sh
Normal file
675
tools/generate-code-docs.sh
Normal 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 "$@"
|
||||
586
tools/project-report.sh
Normal file
586
tools/project-report.sh
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
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"
|
||||
".forgejo"
|
||||
"node_modules"
|
||||
"vendor"
|
||||
"Library"
|
||||
"Temp"
|
||||
"Logs"
|
||||
"obj"
|
||||
"bin"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
if ((${#EXCLUDES[@]} > 0)); then
|
||||
find_args+=("(")
|
||||
for exclude in "${EXCLUDES[@]}"; do
|
||||
find_args+=(-name "$exclude" -o)
|
||||
done
|
||||
unset 'find_args[${#find_args[@]}-1]'
|
||||
find_args+=(")" -prune -o)
|
||||
fi
|
||||
|
||||
find_args+=(-type f -print0)
|
||||
find "${find_args[@]}"
|
||||
}
|
||||
|
||||
search_tree() {
|
||||
local pattern="$1"
|
||||
if have_tool rg; then
|
||||
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"
|
||||
fi
|
||||
}
|
||||
|
||||
append_unique() {
|
||||
local value="$1"
|
||||
shift
|
||||
local existing
|
||||
|
||||
for existing in "$@"; do
|
||||
if [[ "$existing" == "$value" ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
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+=("$rel")
|
||||
;;
|
||||
start*.sh|launch*.sh|run*.sh|startup*.sh|*.bat|*.cmd)
|
||||
STARTUP_SCRIPTS+=("$rel")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_config_files() {
|
||||
local file="$1"
|
||||
local rel
|
||||
|
||||
rel="$(relative_path "$file")"
|
||||
|
||||
case "$(basename "$file")" in
|
||||
.env|.env.*)
|
||||
ENV_FILES+=("$rel")
|
||||
;;
|
||||
Dockerfile|docker-compose.yml|docker-compose.yaml|compose.yml|compose.yaml)
|
||||
DOCKER_FILES+=("$rel")
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$file" in
|
||||
*.conf|*.config|*.ini|*.env|*.yaml|*.yml|*.json|*config*.php|*config*.py|*config*.rb|*settings*.php)
|
||||
CONFIG_FILES+=("$rel")
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$file" in
|
||||
*database*|*db*config*|*mysql*|*pgsql*|*postgres*|*sqlite*|*mongo*)
|
||||
DB_CONFIGS+=("$rel")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
classify_language() {
|
||||
local file="$1"
|
||||
case "$file" in
|
||||
*.php) LANGUAGE_COUNTS["PHP"]=$((LANGUAGE_COUNTS["PHP"] + 1)) ;;
|
||||
*.js|*.mjs|*.cjs) LANGUAGE_COUNTS["JavaScript"]=$((LANGUAGE_COUNTS["JavaScript"] + 1)) ;;
|
||||
*.ts|*.tsx) LANGUAGE_COUNTS["TypeScript"]=$((LANGUAGE_COUNTS["TypeScript"] + 1)) ;;
|
||||
*.py) LANGUAGE_COUNTS["Python"]=$((LANGUAGE_COUNTS["Python"] + 1)) ;;
|
||||
*.cs) LANGUAGE_COUNTS["C#"]=$((LANGUAGE_COUNTS["C#"] + 1)) ;;
|
||||
*.cpp|*.cc|*.cxx|*.c|*.h|*.hpp) LANGUAGE_COUNTS["C++"]=$((LANGUAGE_COUNTS["C++"] + 1)) ;;
|
||||
*.rb) LANGUAGE_COUNTS["Ruby"]=$((LANGUAGE_COUNTS["Ruby"] + 1)) ;;
|
||||
*.pl|*.pm) LANGUAGE_COUNTS["Perl"]=$((LANGUAGE_COUNTS["Perl"] + 1)) ;;
|
||||
*.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
|
||||
}
|
||||
|
||||
detect_frameworks() {
|
||||
local frameworks=()
|
||||
|
||||
[[ -f "$ROOT_DIR/artisan" ]] && frameworks+=("Laravel")
|
||||
[[ -d "$ROOT_DIR/app/Config" ]] && frameworks+=("CodeIgniter")
|
||||
[[ -f "$ROOT_DIR/wp-config.php" ]] && frameworks+=("WordPress")
|
||||
[[ -f "$ROOT_DIR/manage.py" ]] && frameworks+=("Django")
|
||||
[[ -f "$ROOT_DIR/app.py" ]] && search_tree "Flask" && frameworks+=("Flask")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && frameworks+=("Node.js")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"express\"" && frameworks+=("Express")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"react\"" && frameworks+=("React")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"vue\"" && frameworks+=("Vue")
|
||||
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print | grep -q . 2>/dev/null && frameworks+=("ASP.NET")
|
||||
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name 'ProjectSettings.asset' -type f -print | grep -q . 2>/dev/null && frameworks+=("Unity")
|
||||
[[ -d "$ROOT_DIR/src/Symfony" || -f "$ROOT_DIR/bin/console" ]] && frameworks+=("Symfony")
|
||||
|
||||
printf '%s\n' "${frameworks[@]}" | awk 'NF && !seen[$0]++'
|
||||
}
|
||||
|
||||
detect_databases() {
|
||||
local databases=()
|
||||
|
||||
search_tree "mysql" && databases+=("MySQL")
|
||||
search_tree "mariadb" && databases+=("MariaDB")
|
||||
search_tree "postgres|postgresql|pgsql" && databases+=("PostgreSQL")
|
||||
search_tree "sqlite" && databases+=("SQLite")
|
||||
search_tree "mongodb|mongo" && databases+=("MongoDB")
|
||||
|
||||
printf '%s\n' "${databases[@]}" | awk 'NF && !seen[$0]++'
|
||||
}
|
||||
|
||||
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 'CUSTOMER CODE STATUS UNKNOWN\n'
|
||||
fi
|
||||
}
|
||||
|
||||
detect_repo_name() {
|
||||
basename "$ROOT_DIR"
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
record_generated_file "$FILE_REPORT"
|
||||
}
|
||||
|
||||
write_project_flow_report() {
|
||||
local frameworks="$1"
|
||||
local databases="$2"
|
||||
local likely_entry="No obvious entry point detected"
|
||||
|
||||
if ((${#ENTRY_POINTS[@]} > 0)); then
|
||||
likely_entry="${ENTRY_POINTS[0]}"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Project Flow Report\n\n'
|
||||
printf '## Likely Entry Point\n\n'
|
||||
printf -- '- Likely entry point: %s\n' "$likely_entry"
|
||||
|
||||
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 "$@"
|
||||
372
tools/validate-code.sh
Normal file
372
tools/validate-code.sh
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
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"
|
||||
".forgejo"
|
||||
"node_modules"
|
||||
"vendor"
|
||||
"Library"
|
||||
"Temp"
|
||||
"Logs"
|
||||
"obj"
|
||||
"bin"
|
||||
)
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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))
|
||||
DETAILS+=("- Warning: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
add_error() {
|
||||
local message="$1"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
DETAILS+=("- Error: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
add_ok() {
|
||||
local message="$1"
|
||||
DETAILS+=("- OK: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
relative_path() {
|
||||
local path="$1"
|
||||
path="${path#"$ROOT_DIR"/}"
|
||||
printf '%s' "$path"
|
||||
}
|
||||
|
||||
join_by() {
|
||||
local delimiter="$1"
|
||||
shift || true
|
||||
local first=1
|
||||
local item
|
||||
|
||||
for item in "$@"; do
|
||||
[[ -z "$item" ]] && continue
|
||||
|
||||
if [[ $first -eq 1 ]]; then
|
||||
printf '%s' "$item"
|
||||
first=0
|
||||
else
|
||||
printf '%s%s' "$delimiter" "$item"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
collect_files() {
|
||||
local find_args=("$ROOT_DIR")
|
||||
local exclude
|
||||
|
||||
if ((${#EXCLUDES[@]} > 0)); then
|
||||
find_args+=("(")
|
||||
for exclude in "${EXCLUDES[@]}"; do
|
||||
find_args+=(-name "$exclude" -o)
|
||||
done
|
||||
unset 'find_args[${#find_args[@]}-1]'
|
||||
find_args+=(")" -prune -o)
|
||||
fi
|
||||
|
||||
find_args+=(-type f -print0)
|
||||
find "${find_args[@]}"
|
||||
}
|
||||
|
||||
run_check() {
|
||||
local label="$1"
|
||||
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))
|
||||
|
||||
: >"$stdout_file"
|
||||
: >"$stderr_file"
|
||||
|
||||
if "$@" >"$stdout_file" 2>"$stderr_file"; then
|
||||
add_ok "$label passed: $(relative_path "$file")"
|
||||
else
|
||||
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
|
||||
}
|
||||
|
||||
run_repo_build_checks() {
|
||||
local sln_files=()
|
||||
local csproj_files=()
|
||||
local cmake_file="$ROOT_DIR/CMakeLists.txt"
|
||||
local tmp_cmake_dir=""
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
sln_files+=("$file")
|
||||
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 -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# project validation."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$cmake_file" ]]; then
|
||||
if have_tool cmake; then
|
||||
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||
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_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_cmake_dir"
|
||||
else
|
||||
add_warning "cmake not installed; skipping C/C++ validation."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
validate_file() {
|
||||
local file="$1"
|
||||
local base
|
||||
|
||||
base="$(basename "$file")"
|
||||
|
||||
case "$file" in
|
||||
*.php)
|
||||
if have_tool php; then
|
||||
run_check "php -l" "$file" php -l "$file"
|
||||
else
|
||||
add_warning "php not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.py)
|
||||
if have_tool python3; then
|
||||
run_check "python3 -m py_compile" "$file" python3 -m py_compile "$file"
|
||||
else
|
||||
add_warning "python3 not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.rb)
|
||||
if have_tool ruby; then
|
||||
run_check "ruby -c" "$file" ruby -c "$file"
|
||||
else
|
||||
add_warning "ruby not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.pl|*.pm)
|
||||
if have_tool perl; then
|
||||
run_check "perl -c" "$file" perl -c "$file"
|
||||
else
|
||||
add_warning "perl not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.sh|*.bash)
|
||||
if have_tool bash; then
|
||||
run_check "bash -n" "$file" bash -n "$file"
|
||||
else
|
||||
add_warning "bash not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.js|*.mjs|*.cjs)
|
||||
if have_tool node; then
|
||||
run_check "node --check" "$file" node --check "$file"
|
||||
else
|
||||
add_warning "node not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.json)
|
||||
if have_tool jq; then
|
||||
run_check "jq validation" "$file" jq empty "$file"
|
||||
else
|
||||
add_warning "jq not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.yml|*.yaml)
|
||||
if have_tool yamllint; then
|
||||
run_check "yamllint" "$file" yamllint -f parsable "$file"
|
||||
else
|
||||
add_warning "yamllint not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.html|*.htm)
|
||||
if have_tool tidy; then
|
||||
run_check "tidy validation" "$file" tidy -qe "$file"
|
||||
else
|
||||
add_warning "tidy not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [[ "$base" == "Dockerfile" ]]; then
|
||||
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||
add_ok "Detected Dockerfile: $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
record_generated_file "$GENERATED_REPORT"
|
||||
}
|
||||
|
||||
write_status_file() {
|
||||
local status_line="NO CUSTOMER CODE ERRORS FOUND"
|
||||
if ((ERRORS > 0)); then
|
||||
status_line="CUSTOMER CODE ERRORS FOUND"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$status_line" >"$STATUS_FILE" || return 1
|
||||
record_generated_file "$STATUS_FILE"
|
||||
}
|
||||
|
||||
write_error_report() {
|
||||
local status_line="NO CUSTOMER CODE ERRORS FOUND"
|
||||
if ((ERRORS > 0)); then
|
||||
status_line="CUSTOMER CODE ERRORS FOUND"
|
||||
fi
|
||||
|
||||
{
|
||||
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 "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue