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
data/lib/fast.rb CHANGED
@@ -1,28 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
- require 'astrolabe/builder'
5
- require_relative 'fast/rewriter'
6
-
7
- # suppress output to avoid parser gem warnings'
8
- def suppress_output
9
- original_stdout = $stdout.clone
10
- original_stderr = $stderr.clone
11
- $stderr.reopen File.new('/dev/null', 'w')
12
- $stdout.reopen File.new('/dev/null', 'w')
13
- yield
14
- ensure
15
- $stdout.reopen original_stdout
16
- $stderr.reopen original_stderr
17
- end
18
4
 
19
- suppress_output do
20
- require 'parser'
21
- require 'parser/current'
22
- end
5
+ require_relative 'fast/source'
6
+ require_relative 'fast/node'
7
+ require_relative 'fast/rewriter'
23
8
 
24
9
  # Fast is a tool to help you search in the code through the Abstract Syntax Tree
25
10
  module Fast
11
+ NODE_PARENTS = ObjectSpace::WeakMap.new
12
+
13
+ class SyntaxError < StandardError; end
14
+
26
15
  # Literals are shortcuts allowed inside {ExpressionParser}
27
16
  LITERAL = {
28
17
  '...' => ->(node) { node&.children&.any? },
@@ -67,83 +56,95 @@ module Fast
67
56
  %\d # bind extra arguments to the expression
68
57
  /x.freeze
69
58
 
70
- # Set some convention methods from file.
71
- class Node < Astrolabe::Node
72
- # @return [String] with path of the file or simply buffer name.
73
- def buffer_name
74
- expression.source_buffer.name
59
+ class << self
60
+ def ast_node?(node)
61
+ node.respond_to?(:type) && node.respond_to?(:children)
75
62
  end
76
63
 
77
- # @return [Parser::Source::Range] from the expression
78
- def expression
79
- location.expression
64
+ def prism_ast(content, buffer_name: '(string)')
65
+ require_relative 'fast/prism_adapter'
66
+ result = Fast::PrismAdapter.parse(content, buffer_name: buffer_name)
67
+ return result if result
68
+
69
+ prism_errors = Prism.parse(content).errors
70
+ message = prism_errors.map(&:message).uniq.join("\n")
71
+ raise SyntaxError, message
80
72
  end
81
73
 
82
- # @return [String] with the content of the #expression
83
- def source
84
- expression.source
74
+ def parse_ruby(content, buffer_name: '(string)')
75
+ prism_ast(content, buffer_name: buffer_name)
85
76
  end
86
77
 
87
- # @return [Boolean] true if a file exists with the #buffer_name
88
- def from_file?
89
- File.exist?(buffer_name)
78
+ def parser_ast(content, buffer_name: '(string)')
79
+ prism_ast(content, buffer_name: buffer_name)
90
80
  end
91
81
 
92
- # @return [Array<String>] with authors from the current expression range
93
- def blame_authors
94
- `git blame -L #{expression.first_line},#{expression.last_line} #{buffer_name}`.lines.map do |line|
95
- line.split('(')[1].split(/\d+/).first.strip
96
- end
82
+ def parser_ast_from_file(file)
83
+ @parser_cache ||= {}
84
+ @parser_cache[file] ||=
85
+ begin
86
+ method =
87
+ if file.end_with?('.sql')
88
+ require_relative 'fast/sql' unless respond_to?(:parse_sql)
89
+ :parse_sql
90
+ else
91
+ :parser_ast
92
+ end
93
+ Fast.public_send(method, IO.read(file), buffer_name: file)
94
+ end
97
95
  end
98
96
 
99
- # @return [String] with the first element from #blame_authors
100
- def author
101
- blame_authors.first
97
+ def parse_file(file)
98
+ return parser_ast_from_file(file) if file.end_with?('.sql')
99
+
100
+ @cache ||= {}
101
+ @cache[file] ||=
102
+ begin
103
+ parse_ruby(IO.read(file), buffer_name: file)
104
+ end
102
105
  end
103
106
 
104
- # Search recursively into a node and its children using a pattern.
105
- # @param [String] pattern
106
- # @param [Array] *args extra arguments to interpolate in the pattern.
107
- # @return [Array<Fast::Node>>] with files and results
108
- def search(pattern, *args)
109
- Fast.search(pattern, self, *args)
107
+ def summary(code_or_ast, file: nil, command_name: '.summary', level: nil)
108
+ require_relative 'fast/summary'
109
+ Summary.new(code_or_ast, file: file, command_name: command_name, level: level)
110
110
  end
111
111
 
112
- # Captures elements from search recursively
113
- # @param [String] pattern
114
- # @param [Array] *args extra arguments to interpolate in the pattern.
115
- # @return [Array<Fast::Node>>] with files and results
116
- def capture(pattern, *args)
117
- Fast.capture(pattern, self, *args)
112
+ def scan(locations, command_name: '.scan', level: nil)
113
+ require_relative 'fast/scan'
114
+ Scan.new(locations, command_name: command_name, level: level)
118
115
  end
119
- end
120
116
 
121
- # Custom builder allow us to set a buffer name for each Node
122
- class Builder < Astrolabe::Builder
123
- attr_writer :buffer_name
124
- # Generates {Node} from the given information.
125
- #
126
- # @return [Node] the generated node
127
- def n(type, children, source_map)
128
- Node.new(type, children, location: source_map, buffer_name: @buffer_name)
117
+ def parser_class
118
+ raise NoMethodError, 'Fast.parser_class was removed; Fast now parses Ruby with Prism'
119
+ end
120
+
121
+ def parser_require_path
122
+ raise NoMethodError, 'Fast.parser_require_path was removed; Fast now parses Ruby with Prism'
123
+ end
124
+
125
+ def parser_const_name
126
+ raise NoMethodError, 'Fast.parser_const_name was removed; Fast now parses Ruby with Prism'
127
+ end
128
+
129
+ def parser_version_supported?(const_name)
130
+ raise NoMethodError, "Fast.parser_version_supported?(#{const_name.inspect}) was removed; Fast now parses Ruby with Prism"
129
131
  end
130
- end
131
132
 
132
- class << self
133
133
  # @return [Fast::Node] from the parsed content
134
134
  # @example
135
135
  # Fast.ast("1") # => s(:int, 1)
136
136
  # Fast.ast("a.b") # => s(:send, s(:send, nil, :a), :b)
137
137
  def ast(content, buffer_name: '(string)')
138
- buffer = Parser::Source::Buffer.new(buffer_name)
139
- buffer.source = content
140
- Parser::CurrentRuby.new(builder_for(buffer_name)).parse(buffer)
138
+ parse_ruby(content, buffer_name: buffer_name)
139
+ end
140
+
141
+ def validate_ruby!(content, buffer_name: '(string)')
142
+ prism_ast(content, buffer_name: buffer_name)
143
+ true
141
144
  end
142
145
 
143
146
  def builder_for(buffer_name)
144
- builder = Builder.new
145
- builder.buffer_name = buffer_name
146
- builder
147
+ raise NoMethodError, "Fast.builder_for(#{buffer_name.inspect}) was removed; Fast now parses Ruby with Prism"
147
148
  end
148
149
 
149
150
  # @return [Fast::Node] parsed from file content
@@ -152,18 +153,7 @@ module Fast
152
153
  # @example
153
154
  # Fast.ast_from_file("example.rb") # => s(...)
154
155
  def ast_from_file(file)
155
- @cache ||= {}
156
- @cache[file] ||=
157
- begin
158
- method =
159
- if file.end_with?('.sql')
160
- require_relative 'fast/sql' unless respond_to?(:parse_sql)
161
- :parse_sql
162
- else
163
- :ast
164
- end
165
- Fast.public_send(method, IO.read(file), buffer_name: file)
166
- end
156
+ parse_file(file)
167
157
  end
168
158
 
169
159
  # Verify if a given AST matches with a specific pattern
@@ -230,12 +220,24 @@ module Fast
230
220
  # @return [Hash[String, Array]] with files and results
231
221
  def group_results(group_files, locations, parallel: true)
232
222
  files = ruby_files_from(*locations)
233
- if parallel
234
- require 'parallel' unless defined?(Parallel)
235
- Parallel.map(files, &group_files)
236
- else
237
- files.map(&group_files)
238
- end.compact.inject(&:merge!)
223
+ results =
224
+ if parallel
225
+ require 'parallel' unless defined?(Parallel)
226
+ Parallel.map(files) do |file|
227
+ group_files.call(file)
228
+ rescue StandardError => e
229
+ warn "Error processing #{file}: #{e.message}" if Fast.debugging
230
+ nil
231
+ end
232
+ else
233
+ files.map do |file|
234
+ group_files.call(file)
235
+ rescue StandardError => e
236
+ warn "Error processing #{file}: #{e.message}" if Fast.debugging
237
+ nil
238
+ end
239
+ end
240
+ results.compact.inject(&:merge!) || {}
239
241
  end
240
242
 
241
243
  # Capture elements from searches in files. Keep in mind you need to use `$`
@@ -285,7 +287,11 @@ module Fast
285
287
  end
286
288
 
287
289
  def expression(string)
288
- ExpressionParser.new(string).parse
290
+ parser = ExpressionParser.new(string)
291
+ res = parser.parse
292
+ raise SyntaxError, parser.error_message if parser.tokens_left?
293
+
294
+ res
289
295
  end
290
296
 
291
297
  attr_accessor :debugging
@@ -330,6 +336,73 @@ module Fast
330
336
  files.reject(&dir_filter)
331
337
  end
332
338
 
339
+ # Folds the AST to a maximum depth, replacing deeper branches with `...`
340
+ # @param node [Fast::Node]
341
+ # @param level [Integer] maximum depth to explore
342
+ # @param current_level [Integer] internal tracker for depth
343
+ # @return [Fast::Node] the folded AST
344
+ def fold_ast(node, level: nil, current_level: 1)
345
+ return node unless ast_node?(node) && node.respond_to?(:updated)
346
+ return node if level.nil?
347
+
348
+ if current_level >= level
349
+ if node.children.any?
350
+ node.updated(nil, [:'...'])
351
+ else
352
+ node
353
+ end
354
+ else
355
+ new_children = node.children.map { |c| fold_ast(c, level: level, current_level: current_level + 1) }
356
+ node.updated(nil, new_children)
357
+ end
358
+ end
359
+
360
+ # Folds ruby source code to a maximum depth, replacing deep block bodies with `# ...`
361
+ # @param node [Fast::Node]
362
+ # @param level [Integer] maximum depth to explore
363
+ # @return [String] the folded source representation
364
+ def fold_source(node, level: 1)
365
+ return node if node.is_a?(String)
366
+ return node.loc.expression.source rescue node.to_s unless ast_node?(node) && node.respond_to?(:loc)
367
+
368
+ source = node.loc.expression.source.dup
369
+ root_begin = node.loc.expression.begin_pos
370
+
371
+ regions = []
372
+
373
+ walker = ->(n, current_level) do
374
+ return unless ast_node?(n)
375
+
376
+ if current_level >= level
377
+ body_node = nil
378
+ case n.type
379
+ when :class, :module, :def, :defs, :block
380
+ body_node = n.children.last
381
+ when :begin
382
+ body_node = n
383
+ end
384
+
385
+ if body_node && body_node.loc && body_node.loc.respond_to?(:expression) && body_node.loc.expression
386
+ regions << [
387
+ body_node.loc.expression.begin_pos - root_begin,
388
+ body_node.loc.expression.end_pos - root_begin
389
+ ]
390
+ return
391
+ end
392
+ end
393
+
394
+ n.children.each { |c| walker.call(c, current_level + 1) if ast_node?(c) }
395
+ end
396
+
397
+ walker.call(node, 1)
398
+
399
+ regions.sort_by { |r| -r[0] }.each do |start_pos, end_pos|
400
+ source[start_pos...end_pos] = "# ..."
401
+ end
402
+
403
+ source
404
+ end
405
+
333
406
  # Extracts a node pattern expression from a given node supressing identifiers and primitive types.
334
407
  # Useful to index abstract patterns or similar code structure.
335
408
  # @see https://jonatas.github.io/fast/similarity_tutorial/
@@ -341,7 +414,7 @@ module Fast
341
414
  # Fast.expression_from(Fast.ast('def name; person.name end')) # => '(def _ (args) (send (send nil _) _))'
342
415
  def expression_from(node)
343
416
  case node
344
- when Parser::AST::Node
417
+ when ->(candidate) { ast_node?(candidate) }
345
418
  children_expression = node.children.map(&method(:expression_from)).join(' ')
346
419
  "(#{node.type}#{" #{children_expression}" if node.children.any?})"
347
420
  when nil, 'nil'
@@ -387,6 +460,7 @@ module Fast
387
460
  # @param expression [String]
388
461
  def initialize(expression)
389
462
  @tokens = expression.scan TOKENIZER
463
+ @nesting = []
390
464
  end
391
465
 
392
466
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -394,22 +468,43 @@ module Fast
394
468
  # rubocop:disable Metrics/MethodLength
395
469
  def parse
396
470
  case (token = next_token)
397
- when '(' then parse_until_peek(')')
398
- when '{' then Any.new(parse_until_peek('}'))
399
- when '[' then All.new(parse_until_peek(']'))
471
+ when '('
472
+ @nesting << ')'
473
+ parse_until_peek(')')
474
+ when '{'
475
+ @nesting << '}'
476
+ Any.new(parse_until_peek('}'))
477
+ when '['
478
+ @nesting << ']'
479
+ All.new(parse_until_peek(']'))
480
+ when ')', '}', ']'
481
+ raise SyntaxError, "Unexpected token: #{token}"
400
482
  when /^"/ then FindString.new(token[1..-2])
401
483
  when /^#\w/ then MethodCall.new(token[1..])
402
484
  when /^\.\w[\w\d_]+\?/ then InstanceMethodCall.new(token[1..])
403
485
  when '$' then Capture.new(parse)
404
- when '!' then (@tokens.any? ? Not.new(parse) : Find.new(token))
486
+ when '!' then (@tokens.any? && !closing_token?(@tokens.first) ? Not.new(parse) : Find.new(token))
405
487
  when '?' then Maybe.new(parse)
406
488
  when '^' then Parent.new(parse)
407
489
  when '\\' then FindWithCapture.new(parse)
408
490
  when /^%\d/ then FindFromArgument.new(token[1..])
491
+ when nil then nil
409
492
  else Find.new(token)
410
493
  end
411
494
  end
412
495
 
496
+ def tokens_left?
497
+ @tokens.any? || @nesting.any?
498
+ end
499
+
500
+ def error_message
501
+ if @tokens.any?
502
+ "Unconsumed tokens after parsing: #{@tokens.join(' ')}"
503
+ elsif @nesting.any?
504
+ "Unclosed nesting: expected #{@nesting.reverse.join(', ')}"
505
+ end
506
+ end
507
+
413
508
  # rubocop:enable Metrics/CyclomaticComplexity
414
509
  # rubocop:enable Metrics/AbcSize
415
510
  # rubocop:enable Metrics/MethodLength
@@ -420,10 +515,19 @@ module Fast
420
515
  @tokens.shift
421
516
  end
422
517
 
518
+ def closing_token?(token)
519
+ [')', '}', ']'].include?(token)
520
+ end
521
+
423
522
  def parse_until_peek(token)
424
523
  list = []
425
524
  list << parse until @tokens.empty? || @tokens.first == token
426
- next_token
525
+ last = next_token
526
+ if last == token
527
+ @nesting.pop
528
+ else
529
+ raise SyntaxError, "Expected #{token} but got #{last || 'end of string'}"
530
+ end
427
531
  list
428
532
  end
429
533
  end
@@ -446,8 +550,14 @@ module Fast
446
550
  when Find then expression.match?(node)
447
551
  when Symbol then compare_symbol_or_head(expression, node)
448
552
  when Enumerable
449
- expression.each_with_index.all? do |exp, i|
450
- match_recursive(exp, i.zero? ? node : node.children[i - 1])
553
+ if expression.last == :'...' || expression.last.is_a?(Find) && expression.last.token == '...'
554
+ expression[0...-1].each_with_index.all? do |exp, i|
555
+ match_recursive(exp, i.zero? ? node : node.children[i - 1])
556
+ end
557
+ else
558
+ expression.each_with_index.all? do |exp, i|
559
+ match_recursive(exp, i.zero? ? node : node.children[i - 1])
560
+ end
451
561
  end
452
562
  else
453
563
  node == expression
@@ -456,7 +566,7 @@ module Fast
456
566
 
457
567
  def compare_symbol_or_head(expression, node)
458
568
  case node
459
- when Parser::AST::Node
569
+ when ->(candidate) { Fast.ast_node?(candidate) }
460
570
  node.type == expression.to_sym
461
571
  when String
462
572
  node == expression.to_s
@@ -741,10 +851,17 @@ module Fast
741
851
 
742
852
  # @return [true] if all children matches with tail
743
853
  def match_tail?(tail, child)
744
- tail.each_with_index.all? do |token, i|
745
- prepare_token(token)
746
- token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
747
- end && find_captures
854
+ if tail.last.is_a?(Find) && tail.last.token == '...'
855
+ tail[0...-1].each_with_index.all? do |token, i|
856
+ prepare_token(token)
857
+ token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
858
+ end && find_captures
859
+ else
860
+ tail.each_with_index.all? do |token, i|
861
+ prepare_token(token)
862
+ token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
863
+ end && find_captures
864
+ end
748
865
  end
749
866
 
750
867
  # Look recursively into @param expression to check if the expression is have
data/mkdocs.yml CHANGED
@@ -1,7 +1,11 @@
1
1
  site_name: Fast
2
2
  repo_url: https://github.com/jonatas/fast
3
3
  edit_uri: edit/master/docs/
4
- google_analytics: ['G-YKZDZDNRG2', 'auto']
4
+
5
+ extra:
6
+ analytics:
7
+ provider: google
8
+ property: G-YKZDZDNRG2
5
9
 
6
10
  theme:
7
11
  name: material
@@ -13,10 +17,15 @@ theme:
13
17
  extra_css:
14
18
  - stylesheets/custom.css
15
19
 
20
+ plugins:
21
+ - search
22
+
16
23
  markdown_extensions:
17
24
  - admonition
18
- - codehilite:
19
- guess_lang: false
25
+ - pymdownx.details
26
+ - pymdownx.superfences
27
+ - pymdownx.tabbed:
28
+ alternate_style: true
20
29
  - toc:
21
30
  permalink: true
22
31
  nav:
@@ -27,10 +36,16 @@ nav:
27
36
  - Experiments: experiments.md
28
37
  - Shortcuts: shortcuts.md
29
38
  - Git Integration: git.md
39
+ - Fast for LLMs and Agents: agents.md
40
+ - MCP Server Tutorial: mcp_tutorial.md
30
41
  - Code Similarity: similarity_tutorial.md
42
+ - LLM/Agent Feature TODOs: llm_features.md
31
43
  - Pry Integration: pry-integration.md
32
44
  - Editors' Integration: editors-integration.md
33
45
  - Research: research.md
34
46
  - Ideas: ideas.md
35
47
  - Videos: videos.md
36
- - SQL Support: sql-support.md
48
+ - SQL:
49
+ - Intro: sql/index.md
50
+ - Shortcuts: sql/shortcuts.md
51
+ - About: sql-support.md
@@ -0,0 +1,3 @@
1
+ mkdocs>=1.6,<2.0
2
+ mkdocs-material>=9.5,<10.0
3
+ pymdown-extensions>=10.0,<11.0
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-17 00:00:00.000000000 Z
11
+ date: 2026-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: astrolabe
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: coderay
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -52,20 +38,6 @@ dependencies:
52
38
  - - ">="
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: parser
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: pg_query
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -271,6 +243,9 @@ executables:
271
243
  extensions: []
272
244
  extra_rdoc_files: []
273
245
  files:
246
+ - ".agents/fast-pattern-expert/SKILL.md"
247
+ - ".github/workflows/release.yml"
248
+ - ".github/workflows/ruby.yml"
274
249
  - ".gitignore"
275
250
  - ".projections.json"
276
251
  - ".rspec"
@@ -288,43 +263,28 @@ files:
288
263
  - bin/console
289
264
  - bin/fast
290
265
  - bin/fast-experiment
266
+ - bin/fast-mcp
291
267
  - bin/setup
292
- - docs/command_line.md
293
- - docs/editors-integration.md
294
- - docs/experiments.md
295
- - docs/git.md
296
- - docs/ideas.md
297
- - docs/index.md
298
- - docs/pry-integration.md
299
- - docs/research.md
300
- - docs/shortcuts.md
301
- - docs/similarity_tutorial.md
302
- - docs/sql-support.md
303
- - docs/syntax.md
304
- - docs/videos.md
305
- - docs/walkthrough.md
306
- - examples/build_stubbed_and_let_it_be_experiment.rb
307
- - examples/experimental_replacement.rb
308
- - examples/find_usage.rb
309
- - examples/let_it_be_experiment.rb
310
- - examples/method_complexity.rb
311
- - examples/search_duplicated.rb
312
- - examples/similarity_research.rb
313
- - examples/simple_rewriter.rb
314
- - experiments/let_it_be_experiment.rb
315
- - experiments/remove_useless_hook.rb
316
- - experiments/replace_create_with_build_stubbed.rb
317
268
  - fast.gemspec
269
+ - ideia_blog_post.md
318
270
  - lib/fast.rb
319
271
  - lib/fast/cli.rb
320
272
  - lib/fast/experiment.rb
321
273
  - lib/fast/git.rb
274
+ - lib/fast/mcp_server.rb
275
+ - lib/fast/node.rb
276
+ - lib/fast/prism_adapter.rb
322
277
  - lib/fast/rewriter.rb
278
+ - lib/fast/scan.rb
323
279
  - lib/fast/shortcut.rb
280
+ - lib/fast/source.rb
281
+ - lib/fast/source_rewriter.rb
324
282
  - lib/fast/sql.rb
325
283
  - lib/fast/sql/rewriter.rb
284
+ - lib/fast/summary.rb
326
285
  - lib/fast/version.rb
327
286
  - mkdocs.yml
287
+ - requirements-docs.txt
328
288
  homepage: https://jonatas.github.io/fast/
329
289
  licenses:
330
290
  - MIT
@@ -360,9 +320,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
360
320
  - !ruby/object:Gem::Version
361
321
  version: '0'
362
322
  requirements: []
363
- rubygems_version: 3.4.22
364
- signing_key:
323
+ rubygems_version: 3.5.22
324
+ signing_key:
365
325
  specification_version: 4
366
326
  summary: 'FAST: Find by AST.'
367
327
  test_files: []
368
- ...