ffast 0.2.2 → 0.2.4

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/fast-pattern-expert/SKILL.md +71 -0
  3. data/.github/workflows/release.yml +27 -0
  4. data/.github/workflows/ruby.yml +34 -0
  5. data/.gitignore +2 -0
  6. data/Fastfile +105 -18
  7. data/README.md +21 -7
  8. data/bin/console +1 -1
  9. data/bin/fast-experiment +3 -0
  10. data/bin/fast-mcp +7 -0
  11. data/fast.gemspec +1 -3
  12. data/ideia_blog_post.md +36 -0
  13. data/lib/fast/cli.rb +74 -23
  14. data/lib/fast/experiment.rb +19 -2
  15. data/lib/fast/git.rb +1 -1
  16. data/lib/fast/mcp_server.rb +341 -0
  17. data/lib/fast/node.rb +258 -0
  18. data/lib/fast/prism_adapter.rb +327 -0
  19. data/lib/fast/rewriter.rb +64 -10
  20. data/lib/fast/scan.rb +207 -0
  21. data/lib/fast/shortcut.rb +16 -4
  22. data/lib/fast/source.rb +116 -0
  23. data/lib/fast/source_rewriter.rb +153 -0
  24. data/lib/fast/sql/rewriter.rb +36 -7
  25. data/lib/fast/sql.rb +15 -17
  26. data/lib/fast/summary.rb +440 -0
  27. data/lib/fast/version.rb +1 -1
  28. data/lib/fast.rb +218 -101
  29. data/mkdocs.yml +19 -4
  30. data/requirements-docs.txt +3 -0
  31. metadata +18 -59
  32. data/docs/command_line.md +0 -238
  33. data/docs/editors-integration.md +0 -46
  34. data/docs/experiments.md +0 -155
  35. data/docs/git.md +0 -115
  36. data/docs/ideas.md +0 -70
  37. data/docs/index.md +0 -404
  38. data/docs/pry-integration.md +0 -27
  39. data/docs/research.md +0 -93
  40. data/docs/shortcuts.md +0 -323
  41. data/docs/similarity_tutorial.md +0 -176
  42. data/docs/sql-support.md +0 -253
  43. data/docs/syntax.md +0 -395
  44. data/docs/videos.md +0 -16
  45. data/docs/walkthrough.md +0 -135
  46. data/examples/build_stubbed_and_let_it_be_experiment.rb +0 -51
  47. data/examples/experimental_replacement.rb +0 -46
  48. data/examples/find_usage.rb +0 -26
  49. data/examples/let_it_be_experiment.rb +0 -11
  50. data/examples/method_complexity.rb +0 -37
  51. data/examples/search_duplicated.rb +0 -15
  52. data/examples/similarity_research.rb +0 -58
  53. data/examples/simple_rewriter.rb +0 -6
  54. data/experiments/let_it_be_experiment.rb +0 -9
  55. data/experiments/remove_useless_hook.rb +0 -9
  56. data/experiments/replace_create_with_build_stubbed.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce52e9ab5a9d9da32683b21584af707cf5c87e460f3bd74297e9038ea38c5494
4
- data.tar.gz: 2b68a433982c97f278829b95234aed4ea5e4aec19264f15f9bfbe8615da19c9a
3
+ metadata.gz: 4b5b5e27292bfe589236ccf40952b5da73f4dfc4b35df85220d9beebae7c725e
4
+ data.tar.gz: 6c4466b554e164c447cfb02760c5b6cd262c0f904f7ea8c35897288efa4668ec
5
5
  SHA512:
6
- metadata.gz: 246e52812d6f332636363e9f8dd960fa5e5ad3ecc50666007ae62041a212efc0e17f79dc01a675e15cd862f04bcc6771ac2ede3c8fd98c656b0efe7f34b67ffc
7
- data.tar.gz: 641af33dbc8858631ae458bc53a4c1b7ce08c44da6788d0cdf7c8c3cae571a9bcb20630f3f9527aeea9a6023a920ac8fe8b0df83672b8280ab8415977114ff4b
6
+ metadata.gz: 0f8ccd9bcd621b69847fd1092b0fa08cada32258a2ec1f6b04654744c8930718628955698673577e4431a6fe6146da744bcb6aafdada442aa08bfe8e5ba32780
7
+ data.tar.gz: 263b032df9ed1ea5b8d7cd71e1bd613dc781c53fc1dd10cb8bedbe526d904543e1331bb43f345679954e7ebc404b0bdfb5f965dffd35b74c454038ba7ae0fb31
@@ -0,0 +1,71 @@
1
+ # Skill: Fast Pattern Expert
2
+
3
+ You are an expert in constructing and translating natural language queries into `Fast` AST patterns for Ruby.
4
+
5
+ ## Core Expertise
6
+ - Translating structural descriptions of Ruby code into S-expression based AST patterns.
7
+ - Understanding the Ruby AST (Prism/Parser based).
8
+ - Navigating and searching codebases semantically using `Fast`.
9
+
10
+ ## Syntax Reference
11
+ - `(node_type ...)` : Search for a node of a specific type.
12
+ - `_` : Matches any non-nil node/value.
13
+ - `nil` : Matches exactly `nil`.
14
+ - `...` : When last in a list, matches zero or more remaining children. Elsewhere, matches a node with children.
15
+ - `^` : Navigate to the parent node.
16
+ - `$` : Capture the matched node or value.
17
+ - `{type1 type2}` : Union (OR) - matches if any internal expression matches.
18
+ - `[expr1 expr2]` : Intersection (AND) - matches only if all internal expressions match.
19
+ - `!expr` : Negation (NOT) - matches if the expression does not match.
20
+ - `?expr` : Maybe - matches if the node is nil or matches the expression.
21
+ - `\1`, `\2` : Backreference to previous captures.
22
+ - `#custom_method` : Call a custom Ruby method for validation.
23
+ - `.instance_method?` : Call an instance method on the node for validation (e.g., `.odd?`).
24
+
25
+ ## Common Ruby AST Nodes
26
+ - `(def name (args) body)` : Method definition.
27
+ - `(defs receiver name (args) body)` : Singleton method definition.
28
+ - `(send receiver method_name args...)` : Method call.
29
+ - `(class name superclass body)` : Class definition.
30
+ - `(module name body)` : Module definition.
31
+ - `(const scope name)` : Constant reference (scope is nil for top-level).
32
+ - `(casgn scope name value)` : Constant assignment.
33
+ - `(lvar name)` : Local variable read.
34
+ - `(lvasgn name value)` : Local variable assignment.
35
+ - `(ivar name)` : Instance variable read.
36
+ - `(ivasgn name value)` : Instance variable assignment.
37
+ - `(hash (pair key value)...)` : Hash literal.
38
+ - `(array elements...)` : Array literal.
39
+
40
+ ## Translation Examples
41
+
42
+ ### Methods
43
+ - "Find all methods named 'process'" -> `(def process)`
44
+ - "Find methods with at least 3 arguments" -> `(def _ (args _ _ _ ...))`
45
+ - "Find singleton methods (self.method)" -> `(defs ...)`
46
+ - "Find methods that call 'super'" -> `(def _ _ (send nil :super ...))`
47
+
48
+ ### Classes & Modules
49
+ - "Find classes inheriting from ApplicationController" -> `(class _ (const nil ApplicationController))`
50
+ - "Find classes defined inside the 'User' namespace" -> `(class (const (const nil User) _) ...)`
51
+ - "Find modules that include 'Enumerable'" -> `(module _ (begin < (send nil include (const nil Enumerable)) ...))`
52
+
53
+ ### Method Calls
54
+ - "Find all calls to 'User.find'" -> `(send (const nil User) find ...)`
55
+ - "Find calls to 'where' with a hash argument" -> `(send _ where (hash ...))`
56
+ - "Find calls to 'exit' or 'abort'" -> `(send nil {exit abort} ...)`
57
+
58
+ ### Variables & Constants
59
+ - "Find where the 'DEBUG' constant is assigned" -> `(casgn nil DEBUG)`
60
+ - "Find all uses of instance variable '@user'" -> `(ivar @user)`
61
+ - "Find assignments to '@user'" -> `(ivasgn @user)`
62
+
63
+ ## Strategy for Complex Queries
64
+ 1. **Identify the anchor node**: What is the primary structure? (e.g., a method definition, a specific call).
65
+ 2. **Describe children**: What must be true about its arguments or body?
66
+ 3. **Use Union/Intersection**: Combine multiple constraints using `{}` or `[]`.
67
+ 4. **Capture if needed**: Use `$` if you only want a specific part of the match.
68
+ 5. **Validate**: Always use `validate_fast_pattern` if available to check syntax.
69
+
70
+ ## AST Triage
71
+ If you are unsure of the AST structure for a piece of code, use `Fast.ast("your code snippet")` or `Fast.ast_from_file` to see the s-expression representation. This is the most reliable way to build a pattern.
@@ -0,0 +1,27 @@
1
+ name: Release Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*' # Triggers when a new tag like v0.2.0 is pushed
7
+
8
+ jobs:
9
+ release:
10
+ name: Build and Release
11
+ runs-on: ubuntu-22.04
12
+ permissions:
13
+ contents: read # Required to checkout the code
14
+ id-token: write # Required for RubyGems Trusted Publishing
15
+
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: '3.3'
24
+ bundler-cache: true
25
+
26
+ - name: Publish to RubyGems
27
+ uses: rubygems/release-gem@v1
@@ -0,0 +1,34 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "master" ]
13
+ pull_request:
14
+ branches: [ "master" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-22.04
22
+ strategy:
23
+ matrix:
24
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
25
+
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - name: Set up Ruby
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby-version }}
32
+ bundler-cache: true
33
+ - name: Run tests
34
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -5,8 +5,10 @@
5
5
  /coverage/
6
6
  /doc/
7
7
  /pkg/
8
+ /site/
8
9
  /spec/reports/
9
10
  /tmp/
11
+ .venv/
10
12
 
11
13
  # rspec failure tracking
12
14
  .rspec_status
data/Fastfile CHANGED
@@ -1,4 +1,10 @@
1
1
  # frozen_string_literal: true
2
+ begin
3
+ require 'fast/source'
4
+ rescue LoadError
5
+ nil
6
+ end
7
+
2
8
  # Fastfile is loaded when you start an expression with a dot.
3
9
  #
4
10
  # You can introduce shortcuts or methods that can be embedded during your
@@ -9,7 +15,7 @@ version_file = Dir['lib/*/version.rb'].first
9
15
  Fast.shortcut(:version, '(casgn nil VERSION (str _))', version_file)
10
16
 
11
17
  # Show all classes that inherits Fast::Find
12
- Fast.shortcut(:finders, '(class ... (const nil Find)', 'lib')
18
+ Fast.shortcut(:finders, '(class ... (const nil Find))', 'lib')
13
19
 
14
20
  # You can run shortcuts appending a dot to the shortcut.
15
21
  # $ fast .version
@@ -17,11 +23,13 @@ Fast.shortcut(:finders, '(class ... (const nil Find)', 'lib')
17
23
  # VERSION = '0.1.2'
18
24
 
19
25
  # Simple shortcut that I used often to show how the expression parser works
20
- Fast.shortcut(:parser, '(class (const nil ExpressionParser)', 'lib/fast.rb')
26
+ Fast.shortcut(:parser, '(class (const nil ExpressionParser))', 'lib/fast.rb')
27
+ Fast.shortcut(:sql_parser, '(def parse ...)', 'lib/fast/sql.rb')
21
28
 
22
29
  # Use `fast .bump_version` to rewrite the version file
23
30
  Fast.shortcut :bump_version do
24
- rewrite_file('(casgn nil VERSION (str _)', version_file) do |node|
31
+ new_version = nil
32
+ rewrite_file('(casgn nil VERSION (str _))', version_file) do |node|
25
33
  target = node.children.last.loc.expression
26
34
  pieces = target.source.split('.').map(&:to_i)
27
35
  pieces.reverse.each_with_index do |fragment, i|
@@ -32,7 +40,24 @@ Fast.shortcut :bump_version do
32
40
  pieces[-(i + 1)] = 0
33
41
  end
34
42
  end
35
- replace(target, "'#{pieces.join('.')}'")
43
+ new_version = pieces.join('.')
44
+ replace(target, "'#{new_version}'")
45
+ end
46
+
47
+ print "Commit bumped version v#{new_version}? (y/n) "
48
+ if $stdin.gets.chomp.downcase == 'y'
49
+ system("git add #{version_file} && git commit -m 'Bump version to v#{new_version}'")
50
+
51
+ print "Tag version v#{new_version}? (y/n) "
52
+ if $stdin.gets.chomp.downcase == 'y'
53
+ system("git tag v#{new_version}")
54
+
55
+ print "Push commit and tag to origin? (y/n) "
56
+ if $stdin.gets.chomp.downcase == 'y'
57
+ system("git push origin HEAD")
58
+ system("git push origin v#{new_version}")
59
+ end
60
+ end
36
61
  end
37
62
  end
38
63
 
@@ -57,22 +82,9 @@ Fast.shortcut :intro do
57
82
  Fast.shortcuts[:walk].run
58
83
  end
59
84
 
60
- # Useful for `fast .walk file.md` but not required by the library.
61
- private
62
- def require_or_install_tty_md
63
- require 'tty-markdown'
64
- rescue LoadError
65
- puts 'Installing tty-markdown gem to better engage you :)'
66
- Gem.install('tty-markdown')
67
- puts 'Done! Now, back to our topic \o/'
68
- system('clear')
69
- retry
70
- end
71
-
72
85
  # Interactive command line walkthrough
73
86
  # fast .walk docs/walkthrough.md
74
87
  Fast.shortcut :walk do
75
- require_or_install_tty_md
76
88
  file = ARGV.last
77
89
  execute = ->(line) { system(line) }
78
90
  walk = ->(line) { line.each_char { |c| sleep(0.02) and print(c) } }
@@ -87,7 +99,82 @@ Fast.shortcut :walk do
87
99
  when /^!{3}\s/
88
100
  # Skip warnings that are only for web tutorials
89
101
  else
90
- walk[TTY::Markdown.parse(line)]
102
+ walk[Fast.render_markdown_for_terminal(line)]
91
103
  end
92
104
  end
93
105
  end
106
+
107
+ # Format SQL
108
+ Fast.shortcut :format_sql do
109
+ require 'fast/sql'
110
+ file = ARGV.last
111
+ method = File.exist?(file) ? :parse_sql_file : :parse_sql
112
+ ast = Fast.public_send(method, file)
113
+ ast = ast.first if ast.is_a? Array
114
+ eligible_kw = [:RESERVED_KEYWORD]
115
+ eligible_tokens = [:BY]
116
+
117
+ output = Fast::SQL.replace('_', ast) do |root|
118
+ sb = root.loc.expression.source_buffer
119
+ sb.tokens.each do |token|
120
+ if eligible_kw.include?(token.keyword_kind) || eligible_tokens.include?(token.token)
121
+ range = Fast::Source.range(sb, token.start, token.end)
122
+ replace(range, range.source.upcase)
123
+ end
124
+ end
125
+ end
126
+ require 'fast/cli'
127
+ puts Fast.highlight(output, sql: true)
128
+ end
129
+
130
+ # Anonymize SQL
131
+ # fast .anonymize_sql file.sql
132
+ Fast.shortcut :anonymize_sql do
133
+ require 'fast/sql'
134
+ file = ARGV.last
135
+ method = File.exist?(file) ? :parse_sql_file : :parse_sql
136
+ ast = Fast.public_send(method, file)
137
+ memo = {}
138
+
139
+ relnames = search("(relname $_)", ast).grep(String).uniq
140
+ pattern = "{relname (sval {#{relnames.map(&:inspect).join(' ')}})}"
141
+ puts "searching with #{pattern}"
142
+
143
+ content = Fast::SQL.replace(pattern, ast) do |node|
144
+ new_name = memo[node.source.tr(%|"'|, '')] ||= "x#{memo.size}"
145
+ new_name = "'#{new_name}'" if node.type == :sval
146
+ replace(node.loc.expression, new_name)
147
+ end
148
+ puts Fast.highlight(content, sql: true)
149
+ end
150
+
151
+ # Give all details in a shorter format
152
+ # fast .summary file.rb
153
+ Fast.shortcut :summary do
154
+ file = ARGV.reverse.find { |arg| !arg.start_with?('-') && File.exist?(arg) }
155
+ if file && File.exist?(file)
156
+ Fast.summary(IO.read(file), file: file, level: fast_option_value(ARGV, '-l', '--level')).summarize
157
+ else
158
+ puts "Please provide a valid file to summarize."
159
+ end
160
+ end
161
+
162
+ # Group and classify multiple Ruby files without printing full bodies
163
+ # fast .scan lib app/models --no-color
164
+ Fast.shortcut :scan do
165
+ locations = ARGV.select { |arg| !arg.start_with?('-') && File.exist?(arg) }
166
+ if locations.any?
167
+ Fast.scan(locations, level: fast_option_value(ARGV, '-l', '--level')).scan
168
+ else
169
+ puts "Please provide at least one valid file or directory to scan."
170
+ end
171
+ end
172
+
173
+ def fast_option_value(args, short_name, long_name)
174
+ args.each_with_index do |arg, index|
175
+ return args[index + 1] if arg == short_name || arg == long_name
176
+ return arg.split('=', 2).last if arg.start_with?("#{long_name}=")
177
+ end
178
+
179
+ nil
180
+ end
data/README.md CHANGED
@@ -57,18 +57,18 @@ For example, let's take an `Integer` in Ruby:
57
57
  1
58
58
  ```
59
59
 
60
- It's corresponding s-expression would be:
60
+ Its corresponding s-expression would be:
61
61
 
62
62
  ```ruby
63
63
  s(:int, 1)
64
64
  ```
65
65
 
66
- `s` in `Fast` and `Parser` are a shorthand for creating an `Parser::AST::Node`.
66
+ `s` in `Fast` is a shorthand for creating a `Fast::Node`.
67
67
  Each of these nodes has a `#type` and `#children` contained in it:
68
68
 
69
69
  ```ruby
70
70
  def s(type, *children)
71
- Parser::AST::Node.new(type, children)
71
+ Fast::Node.new(type, children: children)
72
72
  end
73
73
  ```
74
74
 
@@ -198,7 +198,7 @@ def my_method
198
198
  end
199
199
  ```
200
200
 
201
- It's corresponding s-expression would be:
201
+ Its corresponding s-expression would be:
202
202
 
203
203
  ```ruby
204
204
  ast =
@@ -372,7 +372,7 @@ ast = Fast.ast("def name; person.name end")
372
372
  Generally, we use the `location.expression`:
373
373
 
374
374
  ```ruby
375
- ast.location.expression # => #<Parser::Source::Range (string) 0...25>
375
+ ast.location.expression # => #<Fast::Source::Range (string) 0...25>
376
376
  ```
377
377
 
378
378
  But location also brings some metadata about specific fragments:
@@ -383,7 +383,7 @@ ast.location.instance_variables # => [:@keyword, :@operator, :@name, :@end, :@ex
383
383
 
384
384
  Range for the keyword that identifies the method definition:
385
385
  ```ruby
386
- ast.location.keyword # => #<Parser::Source::Range (string) 0...3>
386
+ ast.location.keyword # => #<Fast::Source::Range (string) 0...3>
387
387
  ```
388
388
 
389
389
  You can always pick the source of a source range:
@@ -395,7 +395,7 @@ ast.location.keyword.source # => "def"
395
395
  Or only the method name:
396
396
 
397
397
  ```ruby
398
- ast.location.name # => #<Parser::Source::Range (string) 4...8>
398
+ ast.location.name # => #<Fast::Source::Range (string) 4...8>
399
399
  ast.location.name.source # => "name"
400
400
  ```
401
401
 
@@ -680,6 +680,20 @@ code("a = 1") # => s(:lvasgn, s(:int, 1))
680
680
 
681
681
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
682
682
 
683
+ ## Documentation locally
684
+
685
+ The documentation site is built with MkDocs Material and a few Markdown
686
+ extensions. To run it locally:
687
+
688
+ ```bash
689
+ python3 -m venv .venv
690
+ source .venv/bin/activate
691
+ python3 -m pip install -r requirements-docs.txt
692
+ mkdocs serve
693
+ ```
694
+
695
+ Then open `http://127.0.0.1:8000`.
696
+
683
697
  ## Contributing
684
698
 
685
699
  Bug reports and pull requests are welcome on GitHub at https://github.com/jonatas/fast. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/bin/console CHANGED
@@ -6,7 +6,7 @@ require 'fast'
6
6
  require 'fast/sql'
7
7
 
8
8
  def s(type, *children)
9
- Parser::AST::Node.new(type, children)
9
+ Fast::Node.new(type, children: children)
10
10
  end
11
11
 
12
12
  def code(string)
data/bin/fast-experiment CHANGED
@@ -8,6 +8,7 @@ require 'fast/experiment'
8
8
  require 'coderay'
9
9
 
10
10
  arguments = ARGV
11
+ autoclean = arguments.delete('--autoclean')
11
12
 
12
13
  experiment_files = Fast.ruby_files_from(File.expand_path('../experiments', __dir__))
13
14
  experiment_files.each(&method(:require))
@@ -23,6 +24,7 @@ end
23
24
  if arguments.any?
24
25
  ruby_files = arguments.all? { |e| File.exist?(e) && e.end_with?('.rb') }
25
26
  experiments.each do |experiment|
27
+ experiment.autoclean = autoclean
26
28
  if ruby_files
27
29
  experiment.files = arguments
28
30
  else
@@ -30,4 +32,5 @@ if arguments.any?
30
32
  end
31
33
  end
32
34
  end
35
+ experiments.each { |experiment| experiment.autoclean = autoclean }
33
36
  experiments.each(&:run)
data/bin/fast-mcp ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
5
+ require 'fast/mcp_server'
6
+
7
+ Fast::McpServer.run!
data/fast.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.license = 'MIT'
18
18
 
19
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
- f.match(%r{^(test|spec|features|docs\/(assets|stylesheets))/})
20
+ f.match(%r{^(test|spec|experiments|examples|features|docs|assets|stylesheets|site)/})
21
21
  end
22
22
 
23
23
  spec.post_install_message = <<~THANKS
@@ -42,10 +42,8 @@ Gem::Specification.new do |spec|
42
42
  spec.executables = %w[fast fast-experiment]
43
43
  spec.require_paths = %w[lib experiments]
44
44
 
45
- spec.add_dependency 'astrolabe'
46
45
  spec.add_dependency 'coderay'
47
46
  spec.add_dependency 'parallel'
48
- spec.add_dependency 'parser'
49
47
  spec.add_dependency 'pg_query'
50
48
 
51
49
  spec.add_development_dependency 'bundler'
@@ -0,0 +1,36 @@
1
+ # Empowering LLM Agents with Natural Language to Fast Patterns
2
+
3
+ Today I'm excited to announce a significant set of improvements to `fast`, specifically designed to bridge the gap between human reasoning (and LLM agents) and the technical precision of Ruby AST searching.
4
+
5
+ ## The Problem: AST Patterns are Hard
6
+
7
+ Searching through code using AST patterns is incredibly powerful, but constructing the right S-expression can be daunting. Whether you're a human or an AI agent, getting the syntax just right—especially for complex queries—often involves a lot of trial and error.
8
+
9
+ ## The Solution: NL-to-Fast Translation
10
+
11
+ We've introduced three new tools to solve this:
12
+
13
+ 1. **The `fast-pattern-expert` Skill**: A specialized Gemini CLI skill that provides deep guidance, syntax references, and few-shot examples for translating structural descriptions of Ruby code into valid `Fast` patterns.
14
+ 2. **`validate_fast_pattern` MCP Tool**: A new tool for our Model Context Protocol (MCP) server that allows agents to verify their generated patterns before executing them.
15
+ 3. **`--validate-pattern` CLI Flag**: A quick way for humans to check if their pattern syntax is correct, checking for balanced nesting and valid token types.
16
+
17
+ ### Example in Action:
18
+ **Query:** "Find all classes that inherit from `BaseService` and have a `call` method."
19
+ **Pattern:** `[(class _ (const nil BaseService)) (def call)]`
20
+
21
+ ## Robustness and Precision
22
+
23
+ We've also beefed up the core of `fast`:
24
+ - **Stricter Validation**: The `ExpressionParser` now tracks nesting levels and unconsumed tokens, providing helpful error messages instead of silently failing or returning partial matches.
25
+ - **Improved `...` Matcher**: The `...` literal now acts as a "rest-of-children" matcher when used at the end of a pattern, making it much easier to match methods with any number of arguments.
26
+ - **Graceful Degradation**: Multi-file scans (`.scan`) now degrade gracefully if a single file fails to parse, ensuring your reconnaissance isn't aborted by one unsupported syntax node.
27
+
28
+ ## A Safer Release Workflow
29
+
30
+ On the operational side, I'm moving our gem release process to **GitHub Actions**. By releasing from a clean CI sandbox instead of my local machine, we ensure a much higher level of security and reproducibility. This move minimizes the risk of local environment contamination and provides a transparent, auditable path from source to RubyGems.
31
+
32
+ ## Release 0.2.4
33
+
34
+ To celebrate these features, we are releasing version `0.2.4` today!
35
+
36
+ Happy searching!