#!/bin/bash # Cairnquire - Progress Monitor # Watches the project directory and updates docs/STATUS.md with progress # Exits if no filesystem changes detected between iterations set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="/Users/tim/developer/cairnquire" DOCS_DIR="$PROJECT_DIR/docs" STATUS_FILE="$DOCS_DIR/STATUS.md" HASH_FILE="$DOCS_DIR/.last_state_hash" ITERATION=0 MAX_ITERATIONS=1000 # Safety limit START_TIME=$(date +%s) # Colors for terminal output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" >&2 } warn() { echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" >&2 } error() { echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" >&2 } success() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1" >&2 } # Compute hash of all tracked files (excluding node_modules, .git, etc.) compute_state_hash() { find "$PROJECT_DIR" \ -type f \ -not -path '*/node_modules/*' \ -not -path '*/.git/*' \ -not -path '*/data/*' \ -not -path '*/vendor/*' \ -not -name '.last_state_hash' \ -not -name 'STATUS.md' \ -not -name '*.lock' \ | sort \ | xargs stat -f '%m %N' 2>/dev/null \ | md5 -q } # Check if filesystem changed has_changes() { local current_hash current_hash=$(compute_state_hash) if [[ -f "$HASH_FILE" ]]; then local last_hash last_hash=$(cat "$HASH_FILE") if [[ "$current_hash" == "$last_hash" ]]; then return 1 # No changes fi fi echo "$current_hash" > "$HASH_FILE" return 0 # Changes detected (or first run) } # Count lines of code by language analyze_codebase() { log "Analyzing codebase..." local go_files=0 local go_lines=0 local ts_files=0 local ts_lines=0 local md_files=0 local md_lines=0 local total_files=0 local docker_files=0 # Count Go files if command -v find &> /dev/null; then go_files=$(find "$PROJECT_DIR" -name '*.go' -not -path '*/vendor/*' -not -path '*/node_modules/*' | wc -l) go_lines=$(find "$PROJECT_DIR" -name '*.go' -not -path '*/vendor/*' -not -path '*/node_modules/*' -exec cat {} + 2>/dev/null | wc -l) ts_files=$(find "$PROJECT_DIR" -name '*.ts' -o -name '*.tsx' | grep -v node_modules | wc -l) ts_lines=$(find "$PROJECT_DIR" -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs cat 2>/dev/null | wc -l) md_files=$(find "$PROJECT_DIR" -name '*.md' | wc -l) md_lines=$(find "$PROJECT_DIR" -name '*.md' -exec cat {} + 2>/dev/null | wc -l) docker_files=$(find "$PROJECT_DIR" -name 'Dockerfile*' -o -name 'docker-compose*' | wc -l) total_files=$(find "$PROJECT_DIR" -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/data/*' | wc -l) fi echo "$go_files|$go_lines|$ts_files|$ts_lines|$md_files|$md_lines|$total_files|$docker_files" } # Check git status if available check_git_status() { if [[ -d "$PROJECT_DIR/.git" ]]; then cd "$PROJECT_DIR" local branch=$(git branch --show-current 2>/dev/null || echo "unknown") local uncommitted=$(git status --short 2>/dev/null | wc -l | tr -d ' ') local last_commit=$(git log -1 --format="%h - %s (%cr)" 2>/dev/null || echo "No commits yet") echo "$branch|$uncommitted|$last_commit" else echo "no-git|0|N/A" fi } # Check if Docker is configured check_docker() { if [[ -f "$PROJECT_DIR/docker-compose.yml" ]]; then echo "yes" else echo "no" fi } # Check milestone progress check_milestones() { local completed=0 local in_progress=0 local total=6 if [[ -f "$DOCS_DIR/milestones/milestone-01-foundation.md" ]]; then # Check for markers in milestone files for i in $(seq 1 6); do local file="$DOCS_DIR/milestones/milestone-0${i}-*.md" if ls $file 1> /dev/null 2>&1; then if grep -q "COMPLETED" $file 2>/dev/null; then completed=$((completed + 1)) elif grep -q "IN_PROGRESS" $file 2>/dev/null; then in_progress=$((in_progress + 1)) fi fi done fi echo "$completed|$in_progress|$total" } # Generate code review comments generate_code_review() { log "Scanning for code review items..." local review="" # Check for TODO/FIXME/XXX in code if find "$PROJECT_DIR" -name '*.go' -o -name '*.ts' -o -name '*.tsx' | grep -v node_modules | xargs grep -n 'TODO\|FIXME\|XXX\|HACK' 2>/dev/null | head -20 > /tmp/todos.txt; then local todo_count=$(wc -l < /tmp/todos.txt | tr -d ' ') if [[ $todo_count -gt 0 ]]; then review+="### Action Items Found ($todo_count)\n\n" review+="| File | Line | Comment |\n" review+="|------|------|---------|\n" while IFS=: read -r file line comment; do # Extract just the filename local fname=$(basename "$file") review+="| $fname | $line | $comment |\n" done < /tmp/todos.txt review+="\n" fi fi # Check for security issues if find "$PROJECT_DIR" -name '*.go' | xargs grep -l 'http.Get\|http.Post\|ioutil.ReadFile' 2>/dev/null | head -5 > /tmp/security.txt; then local sec_count=$(wc -l < /tmp/security.txt | tr -d ' ') if [[ $sec_count -gt 0 ]]; then review+="### Security Review Notes\n\n" review+="- Found $sec_count files using potentially unsafe operations\n" review+="- Ensure all HTTP requests have timeouts\n" review+="- Validate all file paths before reading\n\n" fi fi # Check for error handling if find "$PROJECT_DIR" -name '*.go' | xargs grep -n '_ = ' 2>/dev/null | head -10 > /tmp/errors.txt; then local err_count=$(wc -l < /tmp/errors.txt | tr -d ' ') if [[ $err_count -gt 0 ]]; then review+="### Error Handling ($err_count ignored errors)\n\n" review+="Several errors are being silently ignored. Consider handling or logging these.\n\n" fi fi echo -e "$review" } # Generate the status report generate_status() { local iteration=$1 local now=$(date '+%Y-%m-%d %H:%M:%S UTC') local elapsed=$(( $(date +%s) - START_TIME )) local elapsed_str="$((elapsed / 3600))h $(((elapsed % 3600) / 60))m" # Gather metrics IFS='|' read -r go_files go_lines ts_files ts_lines md_files md_lines total_files docker_files <<< "$(analyze_codebase)" IFS='|' read -r git_branch uncommitted last_commit <<< "$(check_git_status)" IFS='|' read -r completed in_progress total_milestones <<< "$(check_milestones)" local has_docker=$(check_docker) # Generate code review local review=$(generate_code_review) # Calculate progress percentage local progress=$((completed * 100 / total_milestones)) cat > "$STATUS_FILE" << EOF # Project Status Report **Generated:** $now **Monitoring Duration:** $elapsed_str **Iteration:** $iteration **Branch:** $git_branch ## Quick Stats | Metric | Value | |--------|-------| | Total Files | $total_files | | Go Files | $go_files (${go_lines} lines) | | TypeScript Files | $ts_files (${ts_lines} lines) | | Markdown Files | $md_files (${md_lines} lines) | | Docker Config | $has_docker | | Uncommitted Changes | $uncommitted | | Milestones Complete | $completed/$total_milestones (${progress}%) | ## Recent Activity **Last Commit:** $last_commit ## Milestone Progress - [ ] Milestone 1: Foundation - [ ] Milestone 2: Sync Protocol - [ ] Milestone 3: Authentication - [ ] Milestone 4: Collaboration - [ ] Milestone 5: Search & UI - [ ] Milestone 6: Production **Current Focus:** $([[ $in_progress -gt 0 ]] && echo "In Progress" || echo "Not Started") $review ## Notes for Human Review EOF # Add contextual notes based on what we see if [[ $go_files -eq 0 ]]; then echo ">> **No Go files found.** Codex may be working on other aspects or hasn't started yet." >> "$STATUS_FILE" elif [[ $go_files -lt 5 ]]; then echo ">> **Early stage.** Only $go_files Go files exist. Foundation work likely in progress." >> "$STATUS_FILE" else echo ">> **Active development.** $go_files Go files with ${go_lines} lines of code." >> "$STATUS_FILE" fi if [[ $uncommitted -gt 0 ]]; then echo ">> **$uncommitted uncommitted changes** detected. Codex may be mid-work." >> "$STATUS_FILE" fi if [[ -n "$review" ]]; then echo ">> **Code review items found.** See Action Items above." >> "$STATUS_FILE" fi cat >> "$STATUS_FILE" << EOF ## Files Changed This Session EOF # List recently modified files if [[ -f "$HASH_FILE" ]]; then find "$PROJECT_DIR" \ -type f \ -mtime -0.007 \ -not -path '*/node_modules/*' \ -not -path '*/.git/*' \ -not -path '*/data/*' \ -not -name '.last_state_hash' \ -not -name 'STATUS.md' \ | sort | head -20 \ | sed 's|^| - |' >> "$STATUS_FILE" || true fi cat >> "$STATUS_FILE" << EOF --- *This report auto-generated by Cairnquire progress monitor.* *Next update in ~10 minutes or when filesystem changes detected.* EOF success "Status report updated: $STATUS_FILE" } # Main monitoring loop main() { log "Starting Cairnquire progress monitor..." log "Project: $PROJECT_DIR" log "Watching for filesystem changes every 10 minutes" log "Press Ctrl+C to stop, or wait for auto-exit on inactivity" # Ensure docs directory exists mkdir -p "$DOCS_DIR" while true; do ITERATION=$((ITERATION + 1)) if [[ $ITERATION -gt $MAX_ITERATIONS ]]; then warn "Maximum iterations ($MAX_ITERATIONS) reached. Stopping." exit 0 fi log "Iteration $ITERATION - checking for changes..." if [[ $ITERATION -gt 1 ]] && ! has_changes; then warn "No filesystem changes detected since last check." warn "Stopping monitor (Codex may have finished or is inactive)." # Add final note to status cat >> "$STATUS_FILE" << EOF ## Monitor Stopped **Stopped:** $(date '+%Y-%m-%d %H:%M:%S UTC') **Reason:** No filesystem activity detected No changes were observed between this check and the previous one. This could mean: - Codex has completed work - Codex is paused or idle - No files have been modified recently To restart monitoring, run: `./scripts/monitor.sh` EOF exit 0 fi generate_status $ITERATION success "Iteration $ITERATION complete. Sleeping for 10 minutes..." sleep 600 # 10 minutes done } # Handle signals trap 'error "Monitor interrupted"; exit 1' INT TERM # Run main loop main