#!/usr/bin/env bash set -euo pipefail # Colours RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BOLD='\033[1m' RESET='\033[0m' usage() { cat <<'USAGE' Usage: unreflected-logs [OPTIONS] [DIRECTORY] Scan project directories for session logs not yet processed by /reflect-logs. Arguments: DIRECTORY Root directory to scan (default: current directory) Options: -n, --dryrun List discovered projects without checking log status -h, --help Show this help message USAGE } DRYRUN=false DIRECTORY="." while [[ $# -gt 0 ]]; do case "$1" in -n|--dryrun) DRYRUN=true; shift ;; -h|--help) usage; exit 0 ;; -*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; *) DIRECTORY="$1"; shift ;; esac done DIRECTORY="$(cd "$DIRECTORY" && pwd)" # Discovery: find directories containing memory/log/ projects=() while IFS= read -r logdir; do # logdir is /path/to/project/memory/log # project root is two levels up project_root="$(dirname "$(dirname "$logdir")")" projects+=("$project_root") done < <(find "$DIRECTORY" -type d \( -name node_modules -o -path "*/memory/log/*/memory" \) -prune -o -type d -name log -path "*/memory/log" -print 2>/dev/null | sort) if [[ ${#projects[@]} -eq 0 ]]; then echo "No projects with session logs found in $DIRECTORY" exit 0 fi echo "Scanned ${#projects[@]} projects in $DIRECTORY" echo # Dryrun: just list projects if [[ "$DRYRUN" == true ]]; then echo "[dryrun] Would check ${#projects[@]} projects:" for project in "${projects[@]}"; do rel="${project#"$DIRECTORY"/}" [[ "$rel" == "$project" ]] && rel="." echo " $rel" done exit 0 fi # Check phase total_unreflected=0 projects_with_unreflected=0 projects_all_reflected=0 declare -A project_results # project_path -> output string for project in "${projects[@]}"; do rel="${project#"$DIRECTORY"/}" [[ "$rel" == "$project" ]] && rel="." # List log files log_files=() while IFS= read -r f; do log_files+=("$(basename "$f")") done < <(find "$project/memory/log" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort) # Skip if no logs [[ ${#log_files[@]} -eq 0 ]] && continue # Read reflection state state_file="$project/.reflection-state.json" processed_keys=() if [[ -f "$state_file" ]]; then # Extract keys from the "processed" object using python for reliable JSON parsing if processed_output=$(python3 -c " import json, sys try: with open(sys.argv[1]) as f: data = json.load(f) for key in data.get('processed', {}): print(key) except (json.JSONDecodeError, KeyError): sys.exit(1) " "$state_file" 2>/dev/null); then while IFS= read -r key; do processed_keys+=("$key") done <<< "$processed_output" else echo " Warning: malformed .reflection-state.json in $rel" >&2 fi fi # Compare: find unreflected logs unreflected=() for log_file in "${log_files[@]}"; do log_key="log/$log_file" found=false for key in "${processed_keys[@]}"; do if [[ "$key" == "$log_key" ]]; then found=true break fi done if [[ "$found" == false ]]; then unreflected+=("$log_file") fi done if [[ ${#unreflected[@]} -gt 0 ]]; then echo -e "── ${BOLD}${rel}${RESET} ──" echo -e " ${YELLOW}${#unreflected[@]} unreflected${RESET} / ${#log_files[@]} total logs" for u in "${unreflected[@]}"; do echo " - $u" done echo total_unreflected=$((total_unreflected + ${#unreflected[@]})) projects_with_unreflected=$((projects_with_unreflected + 1)) else projects_all_reflected=$((projects_all_reflected + 1)) fi done # Summary if [[ $projects_all_reflected -gt 0 ]]; then echo -e "${GREEN}✓${RESET} $projects_all_reflected projects have all logs reflected." fi if [[ $total_unreflected -eq 0 ]]; then echo -e "\n${GREEN}✓${RESET} All logs reflected across all projects." exit 0 else echo -e "\nTotal: ${YELLOW}${total_unreflected} unreflected logs${RESET} across ${projects_with_unreflected} projects" exit 1 fi