This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Dashboard is a Rails 8.2 application that automatically discovers, analyzes, and tracks all active git repositories in your local development environment. It provides a centralized view via a CLI interface using Rake tasks, extracting metadata about tech stack, deployment status, and project state.
Architecture: SQLite database + ActiveRecord models + Plain Ruby classes (lib/) + Rake tasks interface. No web UI yet (planned for v1.1).
- Ruby: 3.4+
- Rails: 8.2 (main branch)
- Database: SQLite3
- Frontend (future): Tailwind 4, Hotwire (Turbo + Stimulus)
- Background Jobs: Solid Queue
- Cache: Solid Cache
- Asset Pipeline: Propshaft + Importmap
bin/setup # Initial setup
bin/rails db:migrate # Run migrationsbin/rake projects:scan # Scan and save to DB
DRY_RUN=true bin/rake projects:scan # Preview without saving
SCAN_ROOT_PATH=/custom/path bin/rake projects:scan # Custom directory
SCAN_CUTOFF_DAYS=180 bin/rake projects:scan # 6 months instead of 8
bin/rake projects:config # Show configurationbin/rails console # Access models and query data
# Example queries:
# Project.order(last_commit_date: :desc).first(10)
# Project.where("metadata ->> 'inferred_type' = ?", "rails-app")bin/rails test # Run test suite
bin/rails test:system # Run system testsbin/rubocop # Ruby linting (omakase style)
bin/brakeman # Security scanning
bundle audit # Check for gem vulnerabilitiesbin/dev # Start Rails server + Tailwind watch
bin/rails server # Rails server onlyRake Task (lib/tasks/projects.rake)
↓
ProjectScanner (lib/project_scanner.rb)
├─ Finds all .git directories recursively
├─ Filters by commit date (default: 8 months)
└─ For each repo:
↓
ProjectData (lib/project_data.rb)
├─ Extracts git metadata (commits, authors, dates)
├─ Detects tech stack (Gemfile, package.json, etc.)
├─ Infers project type (rails-app, node-app, etc.)
├─ Finds reference files (README, CLAUDE.md, TODO.md)
├─ Assesses current state (active, paused, WIP)
└─ Returns structured metadata
↓
Project Model (app/models/project.rb)
└─ Upserts to SQLite (find_or_initialize_by path)
lib/project_scanner.rb
- Recursive directory traversal to find .git repos
- Filters repos by commit recency
- Skips irrelevant directories (node_modules, hidden dirs)
- Orchestrates scanning loop and database persistence
- Prints progress indicators and summary reports
lib/project_data.rb
- Extracts all metadata for a single repository
- Runs git commands safely (captures stderr)
- Detects tech stack by checking signature files (Gemfile, package.json, etc.)
- Infers project type, deployment status, and current state
- Parses documentation files for descriptions
- Graceful error handling per extraction step
app/models/project.rb
- ActiveRecord model with validations
- Upsert logic via
create_or_update_from_data(project_data) - JSON metadata column for flexible schema
Database Schema:
create_table :projects do |t|
t.string :path # Unique, indexed (repository path)
t.string :name # Project name (directory basename)
t.string :last_commit_date # Indexed for sorting
t.text :last_commit_message
t.json :metadata # All extracted data (tech_stack, commits, etc.)
t.timestamps
endThe metadata JSON column stores:
last_commit_author- Most recent committerrecent_commits- Array of last 10 commits (date + message)commit_count_8m- Commits in last 8 monthscontributors- All unique authorsreference_files- Hash of docs by category (root, ai, cursor, docs)description- Extracted from README/CLAUDE.mdcurrent_state- "active (committed 2 days ago), 3 open tasks"tech_stack- Array: ["ruby", "rails", "node"]inferred_type- rails-app | node-app | python-app | docs | script | unknowndeployment_status- Indicators from Dockerfile, deploy scripts, etc.nested_repos- Subdirectories with their own .giterrors- Array of non-fatal errors during extraction
SCAN_ROOT_PATH- Root directory to scan (default:~/Development)SCAN_CUTOFF_DAYS- Skip projects older than N days (default:240= 8 months)DRY_RUN- Set to"true"to scan without saving (default:false)
lib/directory is autoloaded (excepttasks/)ProjectScannerandProjectDataare available in rake tasks and console- Configured in
config/application.rb:29viaconfig.autoload_lib(ignore: %w[assets tasks])
Signature files checked (in ProjectData#detect_tech_stack):
- Ruby/Rails:
Gemfile(+ check forrailsgem),config/routes.rb - Node/JS:
package.json(+ check forreact,next,vue) - Python:
requirements.txtorpyproject.toml - Go:
go.mod
Project type inference (in ProjectData#infer_project_type):
rails-appif Rails detectednode-appif Node detected (no Rails)python-appif Python detecteddocsifdocs/directory has filesscriptif.rbor.jsfiles in rootunknownotherwise
- Update
ProjectData#extract_metadatato extract new data - No migration needed (JSON column)
- Access via
project.metadata['field_name']or SQL:metadata ->> 'field_name'
Example:
# In lib/project_data.rb
def extract_metadata
# ... existing fields ...
@metadata[:git_remote] = run_git_command("config --get remote.origin.url")
end
# Query in console/code
Project.where("metadata ->> 'git_remote' LIKE ?", "%github.com%")- Update
ProjectData#detect_tech_stackto check for new files - Optionally update
infer_project_typeif new category needed
Example:
def detect_tech_stack
stack = []
# ... existing checks ...
stack << "rust" if File.exist?(File.join(@path, "Cargo.toml"))
stack.uniq
end- Repository-level: Git failures are caught per-repo; repo is skipped but scan continues
- Graceful degradation: Missing files fallback to defaults (e.g., directory name if no README)
- Error tracking: Non-fatal errors stored in
metadata['errors']array - User feedback: Progress indicators (✓ success, ⊗ skipped, ✗ error)
- Sequential processing: ~1-2 repos/second (no parallelization yet)
- File read limits: Max 1000 lines, skip files > 500KB
- Directory pruning: Aggressively skips node_modules, .git internals, hidden dirs
- Database: Indexed queries on
pathandlast_commit_date; JSON operators for metadata - Git caching: Git's own object cache helps with repeated commands
- Future optimization: v1.2 will add incremental updates (only re-scan changed repos)
# All projects, most recent first
Project.order(last_commit_date: :desc)
# Rails projects only
Project.where("metadata ->> 'inferred_type' = ?", "rails-app")
# Active projects (< 7 days old)
Project.select { |p| p.metadata.dig('current_state')&.include?('active') }
# Projects with open TODOs
Project.select { |p| p.metadata.dig('current_state')&.include?('open task') }
# Count by type
Project.all.group_by { |p| p.metadata['inferred_type'] }.transform_values(&:count)
# Most active by commit count
Project.all.sort_by { |p| p.metadata['commit_count_8m'] || 0 }.reverse.first(10)v1.1 - Web UI:
- Dashboard showing active projects (Hotwire + Tailwind)
- Filters by type, tech stack, activity
- Search by name, description
- Click to open in editor/terminal
v1.2 - Incremental Updates:
- Only re-scan changed repos (via mtime or git hooks)
- Background job for scheduled scans (Solid Queue)
v1.3 - Enhanced Analytics:
- Commit frequency graphs
- Contributor leaderboards
- Project relationships (shared contributors)
Why SQLite? Local-first; no external dependencies; file-based portability; fast for < 10k projects.
Why JSON metadata? Schema flexibility during rapid development; easy to add fields without migrations; SQLite has good JSON operators.
Why shell git commands vs git gems? Standard across systems; no dependencies; faster for simple ops; more portable.
Why separate ProjectData from Project? Separation of concerns (extraction vs persistence); ProjectData is plain Ruby with no Rails dependencies; easier to test; could extract to gem later.
"No repositories found"
- Verify
SCAN_ROOT_PATHis correct - Check that directories contain
.gitfolders - Consider if
SCAN_CUTOFF_DAYSis too restrictive
"Projects not saving"
- Run
bin/rails db:migrateto ensure schema is current - Check write permissions on
storage/directory - Look for validation errors in rake task output
"Metadata missing fields"
- Ensure git CLI is installed and in PATH
- Check file read permissions in scanned directories
- Inspect
metadata['errors']for specific failures
"Slow scanning"
- Large repos with many files will be slower
- Network-mounted directories add latency
- Consider narrowing
SCAN_ROOT_PATHto exclude large subtrees