diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..baa53c0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,121 @@ +# AGENTS.md + +PathSpec Ruby - .gitignore-style pattern matching in Ruby + +## Project overview + +Ruby gem implementing .gitignore-style pattern matching with both library API and CLI tool. Supports Ruby 3.2-4.0.1 with comprehensive test coverage and multi-Ruby CI. + +## Setup commands + +```bash +# Install mise (Ruby version manager) +brew install mise # macOS +# Other platforms: https://mise.jdx.dev/getting-started.html + +# Activate mise +eval "$(mise activate zsh)" # or bash, fish, etc. + +# Install Ruby and bundler versions +mise install + +# Install dependencies +mise run install +# or: bundle install +``` + +## Testing + +```bash +# Run all tests (lint, unit, integration, docs) +mise run test +# or: bundle exec rake + +# Unit tests only +mise run test:unit +# or: bundle exec rake spec + +# Integration tests (CLI) only +mise run test:integration +# or: bundle exec rake spec_integration + +# All specs (unit + integration) +mise run test:all +# or: bundle exec rake spec_all + +# Cross-Ruby matrix testing via Docker +mise run test:matrix +# or: bundle exec rake test_matrix +``` + +## Code style + +- Use RuboCop 1.63.5 for linting +- Method length limit: 69 lines +- Use single quotes for strings without interpolation +- Use `%w[]` for word arrays +- Auto-fix with: `bundle exec rubocop --autocorrect` +- For large data arrays: add `# rubocop:disable Metrics/MethodLength` + +## Build and release + +```bash +# Build gem +mise run build +# or: gem build pathspec.gemspec + +# Generate documentation +bundle exec rake docs + +# Development install +rake install +``` + +## Project structure + +``` +pathspec-ruby/ +├── lib/pathspec/ # Core library +│ ├── pathspec.rb # Main PathSpec class +│ └── patterns/ # Pattern implementations +├── bin/pathspec-rb # CLI executable +├── spec/ +│ ├── unit/ # Library tests +│ └── integration/ # CLI tests +├── benchmarks/ # Performance tests +├── docs/ # Documentation source +├── Rakefile # Build tasks +├── .tool-versions # Ruby/bundler versions +└── pathspec.gemspec # Gem spec +``` + +## Development workflow + +1. Make changes to `lib/` code +2. Add/update tests in `spec/unit/` for library changes +3. Add/update tests in `spec/integration/` for CLI changes +4. Run `mise run test` - must pass before committing +5. Fix any RuboCop offenses +6. Test cross-Ruby with `mise run test:matrix` +7. Commit with descriptive messages + +## CLI tool usage + +```bash +bundle exec pathspec-rb -f .gitignore match "file.swp" +bundle exec pathspec-rb -f .gitignore specs_match "file.swp" +bundle exec pathspec-rb -f .gitignore tree ./src +``` + +## Common issues + +**Bundler conflicts**: Always use `mise run install` to ensure correct versions from `.tool-versions` + +**RuboCop failures**: Auto-fix with `bundle exec rubocop --autocorrect`. Method length is common issue - extract large data arrays to separate methods with rubocop:disable comments + +**CI failures**: Check GitHub Actions. RuboCop and integration test failures are most common causes + +## Dependencies + +- **Runtime**: None (pure Ruby) +- **Development**: rspec (~> 3.10), rubocop (~> 1.63.0), fakefs (~> 2.5), kramdown (~> 2.3), benchmark-ips (~> 2.0) \ No newline at end of file diff --git a/benchmarks/pattern_scaling.rb b/benchmarks/pattern_scaling.rb index 279c372..2af3388 100755 --- a/benchmarks/pattern_scaling.rb +++ b/benchmarks/pattern_scaling.rb @@ -16,7 +16,7 @@ def generate_test_paths(count = 1000) # Mix of different path types extensions = %w[.rb .txt .log .tmp .swp .md .yml .json .xml .css .js .html] - directories = ['src', 'lib', 'test', 'spec', 'config', 'docs', 'bin', 'tmp', 'coverage', 'vendor'] + directories = %w[src lib test spec config docs bin tmp coverage vendor] count.times do |i| depth = rand(1..4) @@ -30,7 +30,24 @@ def generate_test_paths(count = 1000) # Generate gitignore patterns of varying complexity def generate_patterns(count) - base_patterns = [ + base_patterns = base_gitignore_patterns + + # Return the first 'count' patterns, cycling if needed + if count <= base_patterns.length + base_patterns.take(count) + else + patterns = base_patterns.dup + remaining = count - base_patterns.length + remaining.times do |i| + patterns << "generated_pattern_#{i}/**/*" + end + patterns + end +end + +# rubocop:disable Metrics/MethodLength +def base_gitignore_patterns + [ '*.log', '*.tmp', '*.swp', @@ -165,31 +182,20 @@ def generate_patterns(count) '.yarn/install-state.gz', '.pnp.*' ] - - # Return the first 'count' patterns, cycling if needed - if count <= base_patterns.length - base_patterns.take(count) - else - patterns = base_patterns.dup - remaining = count - base_patterns.length - remaining.times do |i| - patterns << "generated_pattern_#{i}/**/*" - end - patterns - end end +# rubocop:enable Metrics/MethodLength -puts "PathSpec Performance Benchmark" -puts "=" * 80 -puts "Testing pattern matching performance with varying pattern counts" -puts "Hardware: Apple M4 Pro (12 cores: 8 performance + 4 efficiency), 24 GB RAM" +puts 'PathSpec Performance Benchmark' +puts '=' * 80 +puts 'Testing pattern matching performance with varying pattern counts' +puts 'Hardware: Apple M4 Pro (12 cores: 8 performance + 4 efficiency), 24 GB RAM' puts "Ruby Version: #{RUBY_VERSION}" -puts "Test Configuration:" +puts 'Test Configuration:' puts " - Pattern counts: #{PATTERN_COUNTS.join(', ')}" -puts " - Test paths: 1000 representative file paths" +puts ' - Test paths: 1000 representative file paths' puts " - Warmup time: #{WARMUP_TIME}s" puts " - Benchmark time: #{BENCHMARK_TIME}s per test" -puts "=" * 80 +puts '=' * 80 puts # Pre-generate test data @@ -203,7 +209,7 @@ def generate_patterns(count) pathspec = PathSpec.new(patterns, :git) puts "Benchmarking with #{pattern_count} patterns..." - puts "-" * 80 + puts '-' * 80 results[pattern_count] = {} @@ -211,7 +217,7 @@ def generate_patterns(count) Benchmark.ips do |x| x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME) - x.report("match (single path)") do + x.report('match (single path)') do test_paths.first(10).each do |path| pathspec.match(path) end @@ -226,7 +232,7 @@ def generate_patterns(count) Benchmark.ips do |x| x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME) - x.report("match_paths (100 paths)") do + x.report('match_paths (100 paths)') do pathspec.match_paths(test_paths.first(100), '') end end @@ -237,7 +243,7 @@ def generate_patterns(count) Benchmark.ips do |x| x.config(time: BENCHMARK_TIME, warmup: WARMUP_TIME) - x.report("initialization") do + x.report('initialization') do PathSpec.new(patterns, :git) end end @@ -245,11 +251,11 @@ def generate_patterns(count) puts "\n" end -puts "=" * 80 -puts "Benchmark complete!" -puts "=" * 80 +puts '=' * 80 +puts 'Benchmark complete!' +puts '=' * 80 puts "\nTo analyze results:" -puts "1. Review the iterations/second (i/s) for each pattern count" -puts "2. Compare how performance scales as pattern count increases" -puts "3. Identify which operations are most affected by pattern count" +puts '1. Review the iterations/second (i/s) for each pattern count' +puts '2. Compare how performance scales as pattern count increases' +puts '3. Identify which operations are most affected by pattern count' puts "\nNote: Higher i/s (iterations per second) indicates better performance"