backspin 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -0
  3. data/.gitignore +1 -0
  4. data/.ruby-version +1 -1
  5. data/.standard.yml +5 -0
  6. data/CHANGELOG.md +13 -4
  7. data/Gemfile.lock +27 -25
  8. data/README.md +80 -1
  9. data/Rakefile +27 -0
  10. data/backspin.gemspec +3 -1
  11. data/docs/compare-api-plan.md +193 -0
  12. data/fixtures/projects/dummy_cli_gem/.rspec +3 -0
  13. data/fixtures/projects/dummy_cli_gem/Gemfile +12 -0
  14. data/fixtures/projects/dummy_cli_gem/Gemfile.lock +55 -0
  15. data/fixtures/projects/dummy_cli_gem/LICENSE.txt +1 -0
  16. data/fixtures/projects/dummy_cli_gem/README.md +7 -0
  17. data/fixtures/projects/dummy_cli_gem/Rakefile +8 -0
  18. data/fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec +22 -0
  19. data/fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem +8 -0
  20. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml +18 -0
  21. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml +18 -0
  22. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb +35 -0
  23. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb +5 -0
  24. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb +7 -0
  25. data/fixtures/projects/dummy_cli_gem/mise.toml +2 -0
  26. data/fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb +46 -0
  27. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml +18 -0
  28. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml +18 -0
  29. data/fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt +1 -0
  30. data/fixtures/projects/dummy_cli_gem/spec/spec_helper.rb +22 -0
  31. data/lib/backspin/configuration.rb +13 -0
  32. data/lib/backspin/record.rb +1 -1
  33. data/lib/backspin/recorder.rb +1 -1
  34. data/lib/backspin/version.rb +1 -1
  35. data/lib/backspin.rb +91 -25
  36. data/mise.toml +2 -0
  37. metadata +39 -5
  38. data/script/run_affected_tests +0 -179
@@ -1,179 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "json"
5
- require "pathname"
6
- require "open3"
7
-
8
- class AffectedTestRunner
9
- EXIT_SUCCESS = 0
10
- EXIT_FAILURE = 2
11
-
12
- def initialize
13
- @project_dir = ENV["CLAUDE_PROJECT_DIR"]
14
- validate_environment!
15
- end
16
-
17
- def run
18
- input_data = parse_input
19
- file_path = extract_file_path(input_data)
20
-
21
- return EXIT_SUCCESS unless ruby_file?(file_path)
22
-
23
- validated_path = validate_and_normalize_path(file_path)
24
- return EXIT_SUCCESS unless validated_path
25
-
26
- test_file = determine_test_file(validated_path)
27
- return EXIT_SUCCESS unless test_file
28
-
29
- run_tests(test_file)
30
- rescue => e
31
- abort_to_claude("Unexpected error: #{e.message}")
32
- end
33
-
34
- private
35
-
36
- def validate_environment!
37
- unless @project_dir
38
- abort_to_claude("CLAUDE_PROJECT_DIR environment variable not set")
39
- end
40
-
41
- unless Dir.exist?(@project_dir)
42
- abort_to_claude("CLAUDE_PROJECT_DIR does not exist: #{@project_dir}")
43
- end
44
-
45
- @project_path = Pathname.new(@project_dir).realpath
46
- rescue => e
47
- abort_to_claude("Invalid CLAUDE_PROJECT_DIR: #{e.message}")
48
- end
49
-
50
- def parse_input
51
- input = $stdin.read
52
- JSON.parse(input)
53
- rescue JSON::ParserError => e
54
- abort_to_claude("Invalid JSON input: #{e.message}")
55
- end
56
-
57
- def extract_file_path(data)
58
- file_path = data.dig("tool_input", "file_path")
59
-
60
- unless file_path && !file_path.empty?
61
- abort_to_claude("No file_path provided in input")
62
- end
63
-
64
- file_path
65
- end
66
-
67
- def ruby_file?(file_path)
68
- file_path.end_with?(".rb")
69
- end
70
-
71
- def validate_and_normalize_path(file_path)
72
- # Expand the path to get absolute path
73
- expanded_path = File.expand_path(file_path, @project_dir)
74
- normalized_path = Pathname.new(expanded_path).cleanpath
75
-
76
- # Check if the path is within the project directory
77
- unless normalized_path.to_s.start_with?(@project_path.to_s)
78
- log_info("File path outside project directory: #{file_path}")
79
- return nil
80
- end
81
-
82
- # Convert back to relative path from project root for consistency
83
- normalized_path.relative_path_from(@project_path).to_s
84
- rescue => e
85
- log_info("Invalid file path: #{file_path} - #{e.message}")
86
- nil
87
- end
88
-
89
- def determine_test_file(file_path)
90
- log_info("Determining tests for: #{file_path}")
91
-
92
- if spec_file?(file_path)
93
- # If a spec file was modified, run just that spec
94
- test_file = file_path
95
- log_info("Running single spec: #{test_file}")
96
- elsif lib_file?(file_path)
97
- # If a lib file was modified, try to find corresponding spec
98
- test_file = lib_to_spec_path(file_path)
99
-
100
- if File.exist?(File.join(@project_dir, test_file))
101
- log_info("Running corresponding spec: #{test_file}")
102
- else
103
- # No corresponding spec found, run all tests
104
- log_info("No corresponding spec found, running all tests")
105
- test_file = "spec"
106
- end
107
- else
108
- # For other Ruby files, run all tests
109
- log_info("Running all tests")
110
- test_file = "spec"
111
- end
112
-
113
- # Validate test file/directory exists
114
- full_test_path = File.join(@project_dir, test_file)
115
- unless File.exist?(full_test_path)
116
- abort_to_claude("Test file/directory does not exist: #{test_file}")
117
- end
118
-
119
- test_file
120
- end
121
-
122
- def spec_file?(file_path)
123
- file_path.match?(%r{^.*spec/.*_spec\.rb$})
124
- end
125
-
126
- def lib_file?(file_path)
127
- file_path.match?(%r{^.*lib/.*})
128
- end
129
-
130
- def lib_to_spec_path(lib_path)
131
- # Convert lib/backspin/foo.rb to spec/backspin/foo_spec.rb
132
- lib_path.sub(%r{^(.*/)?lib/}, '\1spec/')
133
- .sub(/\.rb$/, "_spec.rb")
134
- end
135
-
136
- def run_tests(test_file)
137
- rspec_path = File.join(@project_dir, "bin", "rspec")
138
-
139
- unless File.executable?(rspec_path)
140
- abort_to_claude("rspec binary not found or not executable: #{rspec_path}")
141
- end
142
-
143
- log_info("Running: bin/rspec #{test_file}")
144
-
145
- # Change to project directory for command execution
146
- Dir.chdir(@project_dir) do
147
- stdout, stderr, status = Open3.capture3("bin/rspec", test_file)
148
-
149
- # Output both stdout and stderr
150
- $stdout.print stdout
151
- $stderr.print stderr
152
-
153
- if status.success?
154
- log_info("✅ Tests passed")
155
- EXIT_SUCCESS
156
- else
157
- warn("❌ Tests failed - fix before continuing")
158
- EXIT_FAILURE
159
- end
160
- end
161
- rescue => e
162
- abort_to_claude("Failed to run tests: #{e.message}")
163
- end
164
-
165
- def log_info(message)
166
- warn(message)
167
- end
168
-
169
- def abort_to_claude(message)
170
- warn("❌ ERROR: #{message}")
171
- exit EXIT_FAILURE
172
- end
173
- end
174
-
175
- # Run the script
176
- if __FILE__ == $0
177
- runner = AffectedTestRunner.new
178
- exit runner.run
179
- end