#!/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 "$@"