fiber_audit 0.1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.fiber-audit.example.yml +33 -0
  3. data/CHANGELOG.md +32 -0
  4. data/README.md +150 -0
  5. data/bin/fiber-audit +5 -0
  6. data/lib/fiber_audit/audit.rb +288 -0
  7. data/lib/fiber_audit/cli.rb +245 -0
  8. data/lib/fiber_audit/configuration.rb +236 -0
  9. data/lib/fiber_audit/correlation/fingerprint.rb +26 -0
  10. data/lib/fiber_audit/errors.rb +8 -0
  11. data/lib/fiber_audit/execution_context.rb +47 -0
  12. data/lib/fiber_audit/findings/collection.rb +58 -0
  13. data/lib/fiber_audit/findings/confidence.rb +19 -0
  14. data/lib/fiber_audit/findings/evidence.rb +13 -0
  15. data/lib/fiber_audit/findings/finding.rb +76 -0
  16. data/lib/fiber_audit/findings/location.rb +9 -0
  17. data/lib/fiber_audit/findings/severity.rb +19 -0
  18. data/lib/fiber_audit/project.rb +96 -0
  19. data/lib/fiber_audit/reporters/base.rb +12 -0
  20. data/lib/fiber_audit/reporters/json.rb +34 -0
  21. data/lib/fiber_audit/reporters/schema.rb +574 -0
  22. data/lib/fiber_audit/reporters/text.rb +179 -0
  23. data/lib/fiber_audit/static/call_site.rb +71 -0
  24. data/lib/fiber_audit/static/call_site_extractor.rb +524 -0
  25. data/lib/fiber_audit/static/execution_context_resolver.rb +266 -0
  26. data/lib/fiber_audit/static/rules/base.rb +185 -0
  27. data/lib/fiber_audit/static/rules/blocking_subprocess.rb +94 -0
  28. data/lib/fiber_audit/static/rules/built_ins.rb +36 -0
  29. data/lib/fiber_audit/static/rules/direct_socket.rb +112 -0
  30. data/lib/fiber_audit/static/rules/io_select.rb +104 -0
  31. data/lib/fiber_audit/static/rules/net_http_in_request.rb +116 -0
  32. data/lib/fiber_audit/static/rules/registry.rb +123 -0
  33. data/lib/fiber_audit/static/rules/synchronization.rb +124 -0
  34. data/lib/fiber_audit/static/rules/thread_current_state.rb +113 -0
  35. data/lib/fiber_audit/static/rules/thread_join.rb +96 -0
  36. data/lib/fiber_audit/static/semantic_index.rb +300 -0
  37. data/lib/fiber_audit/suppressions/parser.rb +146 -0
  38. data/lib/fiber_audit/suppressions/store.rb +63 -0
  39. data/lib/fiber_audit/version.rb +5 -0
  40. data/lib/fiber_audit.rb +40 -0
  41. metadata +108 -0
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'rubydex'
5
+
6
+ module FiberAudit
7
+ module Static
8
+ # Semantic index backed by Rubydex 0.2.9.
9
+ #
10
+ # === Line/column conventions (FiberAudit standard)
11
+ # * Lines are **one-based** (1, 2, 3, ...).
12
+ # * Columns are **zero-based** (0, 1, 2, ...).
13
+ #
14
+ # Rubydex 0.2.9 reports start_line as **zero-based** and start_column as
15
+ # zero-based. The +normalize_location+ helper adds 1 to start_line so that
16
+ # all returned locations use FiberAudit's one-based line convention.
17
+ class SemanticIndex
18
+ # Data types returned by public seams.
19
+ Declaration = Data.define(:name, :kind, :path, :line)
20
+ Reference = Data.define(:name, :path, :line, :column, :context)
21
+ Constant = Data.define(:name, :path, :line)
22
+ RubydexGap = Data.define(:method, :reason)
23
+
24
+ attr_reader :root, :gaps
25
+
26
+ def initialize(root:)
27
+ @root = Pathname.new(root).expand_path.cleanpath
28
+ @graph = nil
29
+ @gaps = []
30
+ end
31
+
32
+ # Build (or rebuild) the index. Clears and deduplicates gaps on every call.
33
+ def build
34
+ @gaps = []
35
+ @graph = ::Rubydex::Graph.new
36
+ @graph.workspace_path = @root.to_s
37
+ @graph.index_workspace
38
+ @graph.resolve
39
+ record_gaps
40
+ self
41
+ rescue StandardError => e
42
+ @gaps << RubydexGap.new(method: 'build', reason: e.message)
43
+ self
44
+ end
45
+
46
+ # Returns Array[Declaration] for classes/modules/methods in the workspace.
47
+ # Returns one Declaration per workspace definition site so that reopened
48
+ # classes expose both (or more) definition sites.
49
+ def declarations
50
+ return [] unless @graph
51
+
52
+ @graph.declarations.select { |d| workspace_declaration?(d) }.filter_map do |decl|
53
+ kind = declaration_kind(decl)
54
+ workspace_definition_locations(decl).filter_map do |loc|
55
+ path, line = normalize_location(loc)
56
+ next unless path
57
+
58
+ Declaration.new(name: decl.name, kind: kind, path: path, line: line)
59
+ end
60
+ end.flatten
61
+ rescue StandardError
62
+ []
63
+ end
64
+
65
+ # Resolves a constant name to a Constant object.
66
+ # Returns nil if the constant cannot be resolved or is outside the workspace.
67
+ def resolve_constant(name, nesting:)
68
+ return nil unless @graph
69
+
70
+ result = @graph.resolve_constant(name.to_s, nesting || [])
71
+ return nil unless result
72
+
73
+ loc = first_workspace_location(result)
74
+ return nil unless loc
75
+
76
+ path, line = normalize_location(loc)
77
+ Constant.new(name: result.name, path: path, line: line)
78
+ rescue StandardError
79
+ nil
80
+ end
81
+
82
+ # Returns Array[String] of ancestor class/module names.
83
+ # Uses Rubydex's +ancestors+ method which considers ALL class/module
84
+ # declarations (including external/framework ancestors) while the target
85
+ # declaration itself remains workspace-owned (via find_declaration).
86
+ def ancestors_of(name)
87
+ return [] unless @graph
88
+
89
+ decl = find_declaration(name)
90
+ return [] unless decl
91
+ return [] unless decl.respond_to?(:ancestors)
92
+
93
+ ancestors = decl.ancestors
94
+ return [] unless ancestors
95
+
96
+ ancestors.select { |ancestor| class_or_module?(ancestor) && ancestor.name != name }
97
+ .map(&:name)
98
+ rescue StandardError
99
+ []
100
+ end
101
+
102
+ # Returns Array[String] of descendant class/module names.
103
+ # Gracefully returns [] if Rubydex cannot compute descendants.
104
+ def descendants_of(name)
105
+ return [] unless @graph
106
+
107
+ decl = find_declaration(name)
108
+ return [] unless decl
109
+ return [] unless decl.respond_to?(:descendants)
110
+
111
+ descendants = decl.descendants
112
+ return [] unless descendants
113
+
114
+ descendants.map(&:name).uniq - [name]
115
+ rescue StandardError
116
+ []
117
+ end
118
+
119
+ # Returns Array[Reference] for references to a given constant.
120
+ # Filters to workspace and rescues non-file URIs per reference.
121
+ def references_to(name)
122
+ return [] unless @graph
123
+
124
+ @graph.constant_references.filter_map do |ref|
125
+ next unless ref.respond_to?(:declaration) && ref.declaration&.name == name
126
+
127
+ loc = ref.location
128
+ next unless workspace_location?(loc)
129
+
130
+ path, line, column = normalize_location_full(loc)
131
+ Reference.new(name: name, path: path, line: line, column: column, context: nil)
132
+ rescue StandardError
133
+ nil
134
+ end
135
+ rescue StandardError
136
+ []
137
+ end
138
+
139
+ private
140
+
141
+ # ---- Pathname-based workspace containment ----
142
+
143
+ # Check if a file path is within the workspace using Pathname ancestry.
144
+ # This avoids string-prefix false positives like /workspace matching /workspace-other.
145
+ def workspace_path?(file_path)
146
+ return false unless file_path && !file_path.empty?
147
+
148
+ path_obj = Pathname.new(file_path)
149
+ path_obj == @root || path_obj.ascend.any? { |ancestor| ancestor == @root }
150
+ rescue ArgumentError, TypeError
151
+ false
152
+ end
153
+
154
+ # Check if a Rubydex location points to a file within the workspace.
155
+ # Rescues non-file URIs (e.g. gem:// URIs) per location.
156
+ def workspace_location?(loc)
157
+ return false unless loc
158
+
159
+ file_path = location_file_path(loc)
160
+ return false unless file_path
161
+
162
+ workspace_path?(file_path)
163
+ rescue StandardError
164
+ false
165
+ end
166
+
167
+ def workspace_declaration?(decl)
168
+ return false unless decl.respond_to?(:definitions)
169
+
170
+ decl.definitions.any? do |defn|
171
+ workspace_location?(defn.location)
172
+ rescue StandardError
173
+ false
174
+ end
175
+ rescue StandardError
176
+ false
177
+ end
178
+
179
+ # ---- Definition site enumeration ----
180
+
181
+ # Returns all workspace-owned locations from a declaration's definitions.
182
+ # This ensures reopened classes expose all definition sites.
183
+ def workspace_definition_locations(decl)
184
+ return [] unless decl.respond_to?(:definitions)
185
+
186
+ decl.definitions.filter_map do |d|
187
+ d.location if workspace_location?(d.location)
188
+ rescue StandardError
189
+ nil
190
+ end
191
+ rescue StandardError
192
+ []
193
+ end
194
+
195
+ # Returns the first workspace-owned location from a declaration's definitions.
196
+ # Used for constant resolution where only the primary site is needed.
197
+ def first_workspace_location(decl)
198
+ return nil unless decl.respond_to?(:definitions)
199
+
200
+ defn = decl.definitions.find do |d|
201
+ workspace_location?(d.location)
202
+ rescue StandardError
203
+ false
204
+ end
205
+ defn&.location
206
+ rescue StandardError
207
+ nil
208
+ end
209
+
210
+ # ---- Location normalization (FiberAudit: 1-based lines, 0-based columns) ----
211
+
212
+ # Extracts the file system path from a rubydex location.
213
+ # Uses to_file_path which raises NotFileUriError for non-file URIs.
214
+ def location_file_path(loc)
215
+ loc.to_file_path
216
+ rescue ::Rubydex::Location::NotFileUriError, StandardError
217
+ nil
218
+ end
219
+
220
+ # Normalize a rubydex location to [path, line] for Declaration/Constant.
221
+ # Rubydex 0.2.9 reports start_line as zero-based; we add 1 for the
222
+ # FiberAudit one-based line convention.
223
+ def normalize_location(loc)
224
+ path = location_file_path(loc)
225
+ [path, loc.start_line + 1]
226
+ rescue StandardError
227
+ [nil, nil]
228
+ end
229
+
230
+ # Normalize a rubydex location to [path, line, column] for Reference.
231
+ # Rubydex 0.2.9 reports start_line as zero-based; we add 1 for the
232
+ # FiberAudit one-based line convention. Columns remain zero-based.
233
+ def normalize_location_full(loc)
234
+ path = location_file_path(loc)
235
+ [path, loc.start_line + 1, loc.start_column]
236
+ rescue StandardError
237
+ [nil, nil, nil]
238
+ end
239
+
240
+ def declaration_kind(decl)
241
+ case decl
242
+ when defined?(::Rubydex::Class) && ::Rubydex::Class
243
+ :class
244
+ when defined?(::Rubydex::Module) && ::Rubydex::Module
245
+ :module
246
+ when defined?(::Rubydex::Method) && ::Rubydex::Method
247
+ :method
248
+ else
249
+ :unknown
250
+ end
251
+ end
252
+
253
+ def class_or_module?(decl)
254
+ return false unless decl
255
+
256
+ (defined?(::Rubydex::Class) && decl.is_a?(::Rubydex::Class)) ||
257
+ (defined?(::Rubydex::Module) && decl.is_a?(::Rubydex::Module))
258
+ end
259
+
260
+ def find_declaration(name)
261
+ @graph.declarations.find do |d|
262
+ workspace_declaration?(d) && d.name == name.to_s
263
+ end
264
+ rescue StandardError
265
+ nil
266
+ end
267
+
268
+ # ---- Gap management ----
269
+
270
+ # Clears and deduplicates gaps on each build.
271
+ # Uses RubydexGap Data objects (not hashes).
272
+ def record_gaps
273
+ known_gaps = [
274
+ RubydexGap.new(
275
+ method: 'method_name_from_reference',
276
+ reason: 'Rubydex::MethodReference only exposes receiver and location, not the method name being called'
277
+ ),
278
+ RubydexGap.new(
279
+ method: 'ancestors_external',
280
+ reason: 'Rubydex ancestors may not include all external ancestors without explicit dependency indexing'
281
+ ),
282
+ RubydexGap.new(
283
+ method: 'call_site_extraction',
284
+ reason: 'Rubydex tracks method references without names or call context; Prism AST parsing needed'
285
+ ),
286
+ RubydexGap.new(
287
+ method: 'dynamic_methods',
288
+ reason: 'Rubydex may not fully track methods defined via define_method, method_missing, or other metaprogramming'
289
+ ),
290
+ RubydexGap.new(
291
+ method: 'def_delegator_tracking',
292
+ reason: 'Forwardable def_delegator creates method aliases that Rubydex may not fully resolve to their target'
293
+ )
294
+ ]
295
+
296
+ @gaps = (known_gaps + @gaps).uniq
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'prism'
5
+ require_relative '../errors'
6
+
7
+ module FiberAudit
8
+ module Suppressions
9
+ InlineSuppression = Data.define(
10
+ :rule_id, :reason, :path, :start_line, :end_line
11
+ )
12
+ YamlSuppression = Data.define(:rule, :symbol, :operation, :reason)
13
+
14
+ class Parser
15
+ class << self
16
+ # Parses inline suppressions from a file's content using Prism
17
+ # for comment detection.
18
+ # Directives are recognized ONLY from actual Prism comments -
19
+ # never from raw text scans. This ensures directive text in
20
+ # strings, heredocs, and regex literals cannot start or end
21
+ # suppressions.
22
+ def parse_inline(path, content)
23
+ result = Prism.parse(content)
24
+ return [] if result.errors.any?
25
+
26
+ lines = content.lines
27
+ disables = []
28
+ enables = []
29
+
30
+ # Only examine actual comments from Prism
31
+ result.comments.each do |comment|
32
+ text = comment.location.slice
33
+ line_num = comment.location.start_line
34
+
35
+ if (match = text.match(
36
+ /fiber-audit:disable\s+(FA\d+)/
37
+ ))
38
+ rule_id = match[1]
39
+ reason = extract_reason(text, path, line_num)
40
+ is_block = block_comment?(comment, lines)
41
+ disables << {
42
+ rule_id: rule_id,
43
+ reason: reason,
44
+ line: line_num,
45
+ block: is_block
46
+ }
47
+ elsif (match = text.match(
48
+ /fiber-audit:enable\s+(FA\d+)/
49
+ ))
50
+ enables << {
51
+ rule_id: match[1],
52
+ line: line_num
53
+ }
54
+ end
55
+ end
56
+
57
+ build_suppressions(disables, enables, lines, path)
58
+ end
59
+
60
+ # Parse YAML suppressions file
61
+ def parse_yaml(path)
62
+ return [] unless path && File.exist?(path)
63
+
64
+ yaml = YAML.safe_load_file(path) || {}
65
+ raw_suppressions = yaml['suppressions'] || []
66
+
67
+ raw_suppressions.map do |entry|
68
+ rule = entry['rule']
69
+ reason = entry['reason']
70
+
71
+ if reason.nil? || reason.strip.empty?
72
+ raise FiberAudit::ConfigurationError,
73
+ "YAML suppression for rule #{rule} at #{path} " \
74
+ "missing 'reason'"
75
+ end
76
+
77
+ YamlSuppression.new(
78
+ rule: rule,
79
+ symbol: entry['symbol'],
80
+ operation: entry['operation'],
81
+ reason: reason.strip
82
+ )
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Extract and validate reason from comment text
89
+ def extract_reason(text, path, line_number)
90
+ match = text.match(/--\s+(.+)$/)
91
+ if match.nil? || match[1].strip.empty?
92
+ raise FiberAudit::ConfigurationError,
93
+ "Inline suppression at #{path}:#{line_number} " \
94
+ 'missing reason (use -- <reason>)'
95
+ end
96
+ match[1].strip
97
+ end
98
+
99
+ # Determine if comment is a block comment (only whitespace
100
+ # before #) vs a trailing comment (has code before #)
101
+ def block_comment?(comment, lines)
102
+ line = lines[comment.location.start_line - 1]
103
+ prefix = line[0, comment.location.start_column]
104
+ prefix.nil? || prefix.strip.empty?
105
+ end
106
+
107
+ # Build InlineSuppression objects, matching enables to disables
108
+ def build_suppressions(disables, enables, lines, path)
109
+ used_enable_lines = []
110
+
111
+ disables.map do |d|
112
+ end_line = if d[:block]
113
+ enable = find_matching_enable(
114
+ d, enables, used_enable_lines
115
+ )
116
+ if enable
117
+ used_enable_lines << enable[:line]
118
+ enable[:line]
119
+ else
120
+ lines.size
121
+ end
122
+ else
123
+ d[:line]
124
+ end
125
+
126
+ InlineSuppression.new(
127
+ rule_id: d[:rule_id],
128
+ reason: d[:reason],
129
+ path: path,
130
+ start_line: d[:line],
131
+ end_line: end_line
132
+ )
133
+ end
134
+ end
135
+
136
+ def find_matching_enable(disable, enables, used_lines)
137
+ enables.find do |e|
138
+ e[:rule_id] == disable[:rule_id] &&
139
+ e[:line] > disable[:line] &&
140
+ !used_lines.include?(e[:line])
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parser'
4
+
5
+ module FiberAudit
6
+ module Suppressions
7
+ class Store
8
+ attr_reader :inline_suppressions, :yaml_suppressions
9
+
10
+ def initialize(inline_suppressions: [], yaml_suppressions: [])
11
+ @inline_suppressions = inline_suppressions
12
+ @yaml_suppressions = yaml_suppressions
13
+ end
14
+
15
+ # Returns [active_findings, suppressed_findings]
16
+ def apply(findings)
17
+ suppressed = []
18
+ active = []
19
+
20
+ findings.each do |finding|
21
+ if suppressed?(finding)
22
+ suppressed << finding
23
+ else
24
+ active << finding
25
+ end
26
+ end
27
+
28
+ [active, suppressed]
29
+ end
30
+
31
+ private
32
+
33
+ def suppressed?(finding)
34
+ return true if yaml_suppressed?(finding)
35
+ return true if inline_suppressed?(finding)
36
+
37
+ false
38
+ end
39
+
40
+ def yaml_suppressed?(finding)
41
+ @yaml_suppressions.any? do |sup|
42
+ next false unless sup.rule == finding.rule_id
43
+ next false if sup.symbol && sup.symbol != finding.symbol
44
+ next false if sup.operation && sup.operation != finding.operation
45
+
46
+ true
47
+ end
48
+ end
49
+
50
+ def inline_suppressed?(finding)
51
+ return false unless finding.location
52
+
53
+ path = finding.location.path
54
+ line = finding.location.line
55
+
56
+ @inline_suppressions.any? do |sup|
57
+ sup.path == path && line >= sup.start_line && line <= sup.end_line &&
58
+ sup.rule_id == finding.rule_id
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberAudit
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fiber_audit/version'
4
+ require_relative 'fiber_audit/errors'
5
+ require_relative 'fiber_audit/findings/severity'
6
+ require_relative 'fiber_audit/findings/confidence'
7
+ require_relative 'fiber_audit/findings/location'
8
+ require_relative 'fiber_audit/findings/evidence'
9
+ require_relative 'fiber_audit/correlation/fingerprint'
10
+ require_relative 'fiber_audit/findings/finding'
11
+ require_relative 'fiber_audit/findings/collection'
12
+ require_relative 'fiber_audit/configuration'
13
+ require_relative 'fiber_audit/suppressions/parser'
14
+ require_relative 'fiber_audit/suppressions/store'
15
+ require_relative 'fiber_audit/execution_context'
16
+ require_relative 'fiber_audit/static/semantic_index'
17
+ require_relative 'fiber_audit/static/call_site'
18
+ require_relative 'fiber_audit/static/call_site_extractor'
19
+ require_relative 'fiber_audit/static/execution_context_resolver'
20
+ require_relative 'fiber_audit/static/rules/base'
21
+ require_relative 'fiber_audit/static/rules/registry'
22
+ require_relative 'fiber_audit/static/rules/blocking_subprocess'
23
+ require_relative 'fiber_audit/static/rules/thread_join'
24
+ require_relative 'fiber_audit/static/rules/synchronization'
25
+ require_relative 'fiber_audit/static/rules/thread_current_state'
26
+ require_relative 'fiber_audit/static/rules/io_select'
27
+ require_relative 'fiber_audit/static/rules/direct_socket'
28
+ require_relative 'fiber_audit/static/rules/net_http_in_request'
29
+ require_relative 'fiber_audit/static/rules/built_ins'
30
+ require_relative 'fiber_audit/reporters/base'
31
+ require_relative 'fiber_audit/reporters/schema'
32
+ require_relative 'fiber_audit/reporters/text'
33
+ require_relative 'fiber_audit/reporters/json'
34
+ require_relative 'fiber_audit/project'
35
+ require_relative 'fiber_audit/audit'
36
+ require_relative 'fiber_audit/cli'
37
+
38
+ module FiberAudit
39
+ # Public entry point — expanded as work packages are implemented
40
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fiber_audit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - FiberAudit Contributors
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: prism
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubydex
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.0
40
+ executables:
41
+ - fiber-audit
42
+ extensions: []
43
+ extra_rdoc_files: []
44
+ files:
45
+ - ".fiber-audit.example.yml"
46
+ - CHANGELOG.md
47
+ - README.md
48
+ - bin/fiber-audit
49
+ - lib/fiber_audit.rb
50
+ - lib/fiber_audit/audit.rb
51
+ - lib/fiber_audit/cli.rb
52
+ - lib/fiber_audit/configuration.rb
53
+ - lib/fiber_audit/correlation/fingerprint.rb
54
+ - lib/fiber_audit/errors.rb
55
+ - lib/fiber_audit/execution_context.rb
56
+ - lib/fiber_audit/findings/collection.rb
57
+ - lib/fiber_audit/findings/confidence.rb
58
+ - lib/fiber_audit/findings/evidence.rb
59
+ - lib/fiber_audit/findings/finding.rb
60
+ - lib/fiber_audit/findings/location.rb
61
+ - lib/fiber_audit/findings/severity.rb
62
+ - lib/fiber_audit/project.rb
63
+ - lib/fiber_audit/reporters/base.rb
64
+ - lib/fiber_audit/reporters/json.rb
65
+ - lib/fiber_audit/reporters/schema.rb
66
+ - lib/fiber_audit/reporters/text.rb
67
+ - lib/fiber_audit/static/call_site.rb
68
+ - lib/fiber_audit/static/call_site_extractor.rb
69
+ - lib/fiber_audit/static/execution_context_resolver.rb
70
+ - lib/fiber_audit/static/rules/base.rb
71
+ - lib/fiber_audit/static/rules/blocking_subprocess.rb
72
+ - lib/fiber_audit/static/rules/built_ins.rb
73
+ - lib/fiber_audit/static/rules/direct_socket.rb
74
+ - lib/fiber_audit/static/rules/io_select.rb
75
+ - lib/fiber_audit/static/rules/net_http_in_request.rb
76
+ - lib/fiber_audit/static/rules/registry.rb
77
+ - lib/fiber_audit/static/rules/synchronization.rb
78
+ - lib/fiber_audit/static/rules/thread_current_state.rb
79
+ - lib/fiber_audit/static/rules/thread_join.rb
80
+ - lib/fiber_audit/static/semantic_index.rb
81
+ - lib/fiber_audit/suppressions/parser.rb
82
+ - lib/fiber_audit/suppressions/store.rb
83
+ - lib/fiber_audit/version.rb
84
+ homepage: https://github.com/vdombr/fiber_audit
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ rubygems_mfa_required: 'true'
89
+ source_code_uri: https://github.com/vdombr/fiber_audit
90
+ changelog_uri: https://github.com/vdombr/fiber_audit/blob/main/CHANGELOG.md
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 3.3.0
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.6.9
106
+ specification_version: 4
107
+ summary: Static fiber-scheduler compatibility auditor for Ruby and Rails applications
108
+ test_files: []