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